Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/z5labs/gogm

Golang Object Graph Mapper for Neo4j
https://github.com/z5labs/gogm

cli cypher edges go golang graph-mapper neo4j ogm

Last synced: about 2 months ago
JSON representation

Golang Object Graph Mapper for Neo4j

Awesome Lists containing this project

README

        

[![Go Report Card](https://goreportcard.com/badge/github.com/mindstand/gogm/v2)](https://goreportcard.com/report/github.com/mindstand/gogm/v2)
[![Actions Status](https://github.com/mindstand/gogm/workflows/Go/badge.svg)](https://github.com/mindstand/gogm/actions)
[![GoDoc](https://godoc.org/github.com/mindstand/gogm/v2?status.svg)](https://godoc.org/github.com/mindstand/gogm/v2)
# GoGM Golang Object Graph Mapper v2

## Installation
```
go get github.com/mindstand/gogm/v2
```
Note: Do not use `-u` when installing. If you do, gogm will not compile.

This is caused by a dependency being updated to go1.18 making it incompatable with go1.17. In a future update, gogm will be updated to go1.18 eliminating this issue.

## Features
- Struct Mapping through the `gogm` struct decorator
- Full support for ACID transactions
- Underlying connection pooling
- Support for HA Casual Clusters using `bolt+routing` through the [Official Neo4j Go Driver](https://github.com/neo4j/neo4j-go-driver)
- Custom queries in addition to built in functionality
- Builder pattern cypher queries using [MindStand's cypher dsl package](https://github.com/mindstand/go-cypherdsl)
- CLI to generate link and unlink functions for gogm structs.
- Multi database support with Neo4j v4

## What's new in V2
- GoGM is an object now! This means you can have multiple instances of GoGM at a time
- OpenTracing and Context Support
- Driver has been updated from v1.6 to v4
- Log interface, so anyone can use the logger of their choice instead of being forced to use logrus
- Primary Key strategies to use any type of primary key. GoGM is no longer UUID only!
- TLS now supported

| Note: GoGM v1 has been deprecated.

## Usage

### Primary Key Strategy
Primary key strategies allow more customization over primary keys. A strategy is provided to gogm on initialization.


Built in primary key strategies are:
- `gogm.DefaultPrimaryKeyStrategy` -- just use the graph id from neo4j as the primary key
- `gogm.UUIDPrimaryKeyStrategy` -- uuid's as primary keys
```go
// Example of the internal UUID strategy
PrimaryKeyStrategy{
// StrategyName is the name to reference the strategy
StrategyName: "UUID",
// DBName is the name of the field in the database
DBName: "uuid",
// FieldName is the name of the field in go, this will validate to make sure all pk's use the same field name
FieldName: "UUID",
// Type is the reflect type of the primary key, in this case it's a string but it can be any primitive
Type: reflect.TypeOf(""),
// GenIDFunc defines how new ids are generated, in this case we're using googles uuid library
GenIDFunc: func() (id interface{}) {
return uuid.New().String()
},
}
```

### Load Strategy
Load strategies allow control over the queries generated by `Load` operations.
Different strategies change the size of the queries sent to the database as well as the amount of work the database has to do.
A load strategy is provided to gomg on initialization.

The defined load strategies are:
- `gogm.PATH_LOAD_STRATEGY` -- Use cypher path queries to generate simple queries for load operations.
- `gogm.SCHEMA_LOAD_STRATEGY` -- Leverage the GoGM schema to generate more complex queries for load operations which results in less work for the database.

Depending on your use case, `PATH_LOAD_STRATEGY` may result in higher latency.

### Struct Configuration
##### text notates deprecation

Decorators that can be used
- `name=` -- used to set the field name that will show up in neo4j.
- `relationship=` -- used to set the name of the edge on that field.
- `direction=` -- used to specify direction of that edge field.
- `index` -- marks field to have an index applied to it.
- `unique` -- marks field to have unique constraint.
- `pk=` -- marks field as a primary key and specifies which pk strategy to use. Can only have one pk, composite pk's are not supported.
- `properties` -- marks that field is using a map. GoGM only supports properties fields of `map[string]interface{}`, `map[string]`, `map[string][]` and `[]`
- `-` -- marks that field will be ignored by the ogm

#### Not on relationship member variables
All relationships must be defined as either a pointer to a struct or a slice of struct pointers `*SomeStruct` or `[]*SomeStruct`

Use `;` as delimiter between decorator tags.

Ex.

```go
type TdString string

type MyNeo4jObject struct {
// provides required node field
// use gogm.BaseUUIDNode if you want to use UUIDs
gogm.BaseNode

Field string `gogm:"name=field"`
Props map[string]interface{} `gogm:"properties;name=props"` //note that this would show up as `props.` in neo4j
IgnoreMe bool `gogm="-"`
UniqueTypeDef TdString `gogm:"name=unique_type_def"`
Relation *SomeOtherStruct `gogm="relationship=SOME_STRUCT;direction=OUTGOING"`
ManyRelation []*SomeStruct `gogm="relationship=MANY;direction=INCOMING"`
}

```

### GOGM Usage
- Edges must implement the [Edge interface](https://github.com/mindstand/gogm/blob/master/interface.go#L28). View the complete example [here](https://github.com/mindstand/gogm/blob/master/examples/example.go).
```go
package main

import (
"github.com/mindstand/gogm/v2"
"time"
)

type tdString string
type tdInt int

//structs for the example (can also be found in decoder_test.go)
type VertexA struct {
// provides required node fields
gogm.BaseNode

TestField string `gogm:"name=test_field"`
TestTypeDefString tdString `gogm:"name=test_type_def_string"`
TestTypeDefInt tdInt `gogm:"name=test_type_def_int"`
MapProperty map[string]string `gogm:"name=map_property;properties"`
SliceProperty []string `gogm:"name=slice_property;properties"`
SingleA *VertexB `gogm:"direction=incoming;relationship=test_rel"`
ManyA []*VertexB `gogm:"direction=incoming;relationship=testm2o"`
MultiA []*VertexB `gogm:"direction=incoming;relationship=multib"`
SingleSpecA *EdgeC `gogm:"direction=outgoing;relationship=special_single"`
MultiSpecA []*EdgeC `gogm:"direction=outgoing;relationship=special_multi"`
}

type VertexB struct {
// provides required node fields
gogm.BaseNode

TestField string `gogm:"name=test_field"`
TestTime time.Time `gogm:"name=test_time"`
Single *VertexA `gogm:"direction=outgoing;relationship=test_rel"`
ManyB *VertexA `gogm:"direction=outgoing;relationship=testm2o"`
Multi []*VertexA `gogm:"direction=outgoing;relationship=multib"`
SingleSpec *EdgeC `gogm:"direction=incoming;relationship=special_single"`
MultiSpec []*EdgeC `gogm:"direction=incoming;relationship=special_multi"`
}

type EdgeC struct {
// provides required node fields
gogm.BaseNode

Start *VertexA
End *VertexB
Test string `gogm:"name=test"`
}

func main() {
// define your configuration
config := gogm.Config{
Host: "0.0.0.0",
Port: 7687,
// deprecated in favor of protocol
// IsCluster: false,
Protocol: "neo4j", //also supports neo4j+s, neo4j+ssc, bolt, bolt+s and bolt+ssc
// Specify CA Public Key when using +ssc or +s
CAFileLocation: "my-ca-public.crt",
Username: "neo4j",
Password: "password",
PoolSize: 50,
IndexStrategy: gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
TargetDbs: nil,
// default logger wraps the go "log" package, implement the Logger interface from gogm to use your own logger
Logger: gogm.GetDefaultLogger(),
// define the log level
LogLevel: "DEBUG",
// enable neo4j go driver to log
EnableDriverLogs: false,
// enable gogm to log params in cypher queries. WARNING THIS IS A SECURITY RISK! Only use this when debugging
EnableLogParams: false,
// enable open tracing. Ensure contexts have spans already. GoGM does not make root spans, only child spans
OpentracingEnabled: false,
// specify the method gogm will use to generate Load queries
LoadStrategy: gogm.PATH_LOAD_STRATEGY // set to SCHEMA_LOAD_STRATEGY for schema-aware queries which may reduce load on the database
}

// register all vertices and edges
// this is so that GoGM doesn't have to do reflect processing of each edge in real time
// use nil or gogm.DefaultPrimaryKeyStrategy if you only want graph ids
// we are using the default key strategy since our vertices are using BaseNode
_gogm, err := gogm.New(&config, gogm.DefaultPrimaryKeyStrategy, &VertexA{}, &VertexB{}, &EdgeC{})
if err != nil {
panic(err)
}

//param is readonly, we're going to make stuff so we're going to do read write
sess, err := _gogm.NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessModeWrite})
if err != nil {
panic(err)
}

//close the session
defer sess.Close()

aVal := &VertexA{
TestField: "woo neo4j",
}

bVal := &VertexB{
TestTime: time.Now().UTC(),
}

//set bi directional pointer
bVal.Single = aVal
aVal.SingleA = bVal

err = sess.SaveDepth(context.Background(), aVal, 2)
if err != nil {
panic(err)
}

//load the object we just made (save will set the uuid)
var readin VertexA
err = sess.Load(context.Background(), &readin, aVal.UUID)
if err != nil {
panic(err)
}

fmt.Printf("%+v", readin)
}

```

## Migrating from V1 to V2

### Initialization
Initialization in gogm v1
```go
config := gogm.Config{
IndexStrategy: gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
PoolSize: 50,
Port: 7687,
IsCluster: false, //tells it whether or not to use `bolt+routing`
Host: "0.0.0.0",
Password: "password",
Username: "neo4j",
}

err := gogm.Init(&config, &VertexA{}, &VertexB{}, &EdgeC{})
if err != nil {
panic(err)
}
```

Equivalent in GoGM v2
```go
// define your configuration
config := gogm.Config{
IndexStrategy: gogm.VALIDATE_INDEX, //other options are ASSERT_INDEX and IGNORE_INDEX
PoolSize: 50,
Port: 7687,
IsCluster: false, //tells it whether or not to use `bolt+routing`
Host: "0.0.0.0",
Password: "password",
Username: "neo4j",
}

// register all vertices and edges
// this is so that GoGM doesn't have to do reflect processing of each edge in real time
// use nil or gogm.DefaultPrimaryKeyStrategy if you only want graph ids
_gogm, err := gogm.New(&config, gogm.UUIDPrimaryKeyStrategy, &VertexA{}, &VertexB{}, &EdgeC{})
if err != nil {
panic(err)
}

gogm.SetGlobalGoGM(_gogm)
```

##### Note that we call gogm.SetGloablGogm so that we can still access it from a package level

### Create a session
Creating a session in GoGM v1
```go
sess, err := gogm.NewSession(false)
```
##### Note this still works in v2, its using the global gogm to create the session. Also note this is making an instance of the deprecated `ISession`
Equivalent in GoGM v2
```go
// this would also work with a local instance of gogm (localGogm.NewSessionV2)
sess, err := gogm.G().NewSessionV2(gogm.SessionConfig{AccessMode: gogm.AccessModeWrite})
```

### Summary
- Minimal change requires creating a global gogm, everything else should still work with ISession (gogm v1 session object)
- `ISession` is now deprecated but still supported
- SessionV2 is the new standard
### GoGM CLI

## CLI Installation
```
go get -u github.com/mindstand/gogm/v2/cmd/gogmcli
```

## CLI Usage
```
NAME:
gogmcli - used for neo4j operations from gogm schema

USAGE:
gogmcli [global options] command [command options] [arguments...]

VERSION:
2.1.1

COMMANDS:
generate, g, gen to generate link and unlink functions for nodes
help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
--debug, -d execute in debug mode (default: false)
--help, -h show help (default: false)
--version, -v print the version (default: false)
```

## Inspiration
Inspiration came from the Java OGM implementation by Neo4j.

## Road Map
- Schema Migration
- Errors overhaul using go 1.13 error wrapping

## How you can help
- Report Bugs
- Fix bugs
- Contribute (refer to contribute.md)