google / badwolf

Temporal graph store abstraction layer.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Still active?

delaneyj opened this issue · comments

The idea of using an immutable graph store in conjunction with event sourcing make a lot of sense for an upcoming project, however it looks like BadWolf has been abandoned. Is this the case?

@xllora can provide more details, but BadWolf remains actively developed.

Nop. Not sure where we you got the idea :) We are still working on it for the long run. @delaneyj what do you have in mind?

Well the last commit was months ago and no back ends are supported yet so... :)

The system would be at a high level a CMS for legal/health documents. With CQRS/event sourcing you would normally have an event log and a infrastructure around re-hydrating the state. The idea of having an immutable graph of the relationships actually makes more logical sense to me if possible. Had already started looking at doing a combination of (hexastore)[http://karras.rutgers.edu/hexastore.pdf] with an event log.

Fundamentally temporal & immutable seems like a necessity and this project seems to already have a huge head start on that.

Yes, there are not going to be drivers released in this project since that is not its goal. You can find some driver work elsewhere (see the bottom of http://google.github.io/badwolf/) :)

Yes, usually a way I have seen similar constructs are around monitoring some event log to filter and write new facts. Also, with the proper storage.Store wrapper you can also implement triggers for further down processing.

This trigger part is something on the top of the list for the next round of work, although it is there with BQL FR like filters and having condition improvements.

@delaneyj there has been some movement on actually releasing some drivers that we have built.

They are being release in the parallel badwolf-drivers repository. Currently, the main stable ones:

  • Volatile: A volatile memory-based one.
  • BoltDB: A persistent driver build on top of BoltDB.

There is also a branch that has a functional one based on GCP BigTable. We will merge it into the main maste brunch once we have finished ironing out some minor issues needed. But as of now, it should be good enough to take out for a spin :)

Thank you @xllora , just saw this. Will look through the bolt driver, thanks for releasing it!

Can I get some insight into the Triggers mentioned above. That would be very interesting perhaps for implementing live queries through a message queue like NATs and Google pub sub.

Anyway any insight would be useful to help me roadmap and architect Integration.

@gedw99 the idea is very simple. If you check storage.Store and storage.Graph are just interfaces. All BQL is built around them.

You can easily create a wrapper, for instance, for storage.Store. The implementation New method just accepts also storage.Store. Something like

func New(ctx context.Context, st storage.Store) storage.Store { ... }

The implementation of the interface, let's say Wrapper, just delegates the call to the wrapped implementation. Something along this lines.

type Wrapper struct {
  st storage.Store
}

func New(ctx context.Context, st storage.Store) *Wrapper { 
  return &Wrapper{st: st}
}

func (w *Wrapper) Name(ctx context.Context) string {
  return w.st.Name(ctx)
}

func (w *Wrapper) Version(ctx context.Context) string {
  return w.st.Version(ctx)
}

func (w *Wrapper) NewGraph(ctx context.Context, id string) (Graph, error) {
  return w.st.NewGraph(ctx, id)
}

func (w *Wrapper) Graph(ctx context.Context, id string) (Graph, error) {
  return w.st.Graph(ctx, id)
}

func (w *Wrapper) DeleteGraph(ctx context.Context, id string) error {
  return w.st.DeleteGraph(ctx, id)
}

func (w *Wrapper) GraphNames(ctx context.Context, names chan<- string) error {
  return w.st.GraphNames(ctx, names)
}

Now you have everything to add triggers to the operations that change state for instance. Let's assume publisher.Publisher is what would allow you publish to NATS/Google Cloud PubSub. Let's say something along these lines below.

type Wrapper struct {
  st storage.Store
  pub publisher.Publisher
}

func New(ctx context.Context, st storage.Store, pub publisher.Publisher) *Wrapper { 
  return &Wrapper{
    st: st,
    pub: pub,
  }
}

func (w *Wrapper) Name(ctx context.Context) string {
  return w.st.Name(ctx)
}

func (w *Wrapper) Version(ctx context.Context) string {
  return w.st.Version(ctx)
}

func (w *Wrapper) NewGraph(ctx context.Context, id string) (Graph, error) {
  if err:=  w.st.NewGraph(ctx, id); err != nil {
    return err
  }
  // And you can publish it async to avoid latency increase of the op.
  // If you need to guarantee publishing you will need more work here.
  go func() {
    w.pub.Publish(ctx, fmt.Sprintf("new grapd: %d", id))
  }()
  return nil
}

func (w *Wrapper) Graph(ctx context.Context, id string) (Graph, error) {
  return w.st.Graph(ctx, id)
}

func (w *Wrapper) DeleteGraph(ctx context.Context, id string) error {
   if err:=  w.st.DeleteGraph(ctx, id); err != nil {
    return err
  }
  // And you can publish it async to avoid latency increase of the op.
  // If you need to guarantee publishing you will need more work here.
  go func() {
    w.pub.Publish(ctx, fmt.Sprintf("new grapd: %d", id))
  }()
  return nil
}

func (w *Wrapper) GraphNames(ctx context.Context, names chan<- string) error {
  return w.st.GraphNames(ctx, names)
}

Now when you create a new storage driver and properly wrap it. That would look something like this for instance if you would like to add that to the bw tool

// Registers the available drivers.
func registerDrivers() {
  registeredDrivers = map[string]common.StoreGenerator{
     // Memory only storage driver.
    "VOLATILE": func() (storage.Store, error) {
       return memory.NewStore(), nil
    },
    "BWBOLT": func() (storage.Store, error) {
      s, bdb, err := bwbolt.New(*boltDBPath, literal.DefaultBuilder(), *boldTimeout, *boltReadOnly)
      db = bdb
      return s, err
    },
    "WRAPPED_BWBOLT": func() (storage.Store, error) {
       s, bdb, err := bwbolt.New(*boltDBPath, literal.DefaultBuilder(), *boldTimeout, *boltReadOnly)
       if err != nil {
         return nil, err
       }
       db = wrapper.New(context.Background(), bdb, publisher.New())
       return s, err
    },
  }
}

thanks @xllora that looks doable. I will get going on it.