Skip to content

Commit

Permalink
arango db support as external DB(#1429)
Browse files Browse the repository at this point in the history
  • Loading branch information
Umang01-hash authored Feb 6, 2025
1 parent 8ef3bdf commit cd52265
Show file tree
Hide file tree
Showing 28 changed files with 4,488 additions and 19 deletions.
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ docker pull scylladb/scylla
docker run --name scylla -d -p 2025:9042 scylladb/scylla
docker pull surrealdb/surrealdb:latest
docker run --name surrealdb -d -p 8000:8000 surrealdb/surrealdb:latest start --bind 0.0.0.0:8000
docker run -d --name arangodb \
-p 8529:8529 \
-e ARANGO_ROOT_PASSWORD=rootpassword \
--pull always \
arangodb:latest



Expand Down
193 changes: 189 additions & 4 deletions docs/advanced-guide/injecting-databases-drivers/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -908,11 +908,11 @@ func main() {
app.AddSurrealDB(client)

// GET request to fetch person by ID
app.GET("/person/{id}", func(ctx *gofr.Context) (interface{}, error) {
app.GET("/person/{id}", func(ctx *gofr.Context) (any, error) {
id := ctx.PathParam("id")

query := "SELECT * FROM type::thing('person', $id)"
vars := map[string]interface{}{
vars := map[string]any{
"id": id,
}

Expand All @@ -925,14 +925,14 @@ func main() {
})

// POST request to create a new person
app.POST("/person", func(ctx *gofr.Context) (interface{}, error) {
app.POST("/person", func(ctx *gofr.Context) (any, error) {
var person Person

if err := ctx.Bind(&person); err != nil {
return ErrorResponse{Message: "Invalid request body"}, nil
}

result, err := ctx.SurrealDB.Create(ctx, "person", map[string]interface{}{
result, err := ctx.SurrealDB.Create(ctx, "person", map[string]any{
"name": person.Name,
"age": person.Age,
"email": person.Email,
Expand All @@ -949,3 +949,188 @@ func main() {
}

```


## ArangoDB

GoFr supports injecting `ArangoDB` that implements the following interface. Any driver that implements the interface can be
added using the `app.AddArangoDB()` method, and users can use ArangoDB across the application with `gofr.Context`.

```go
type ArangoDB interface {
// CreateDocument creates a new document in the specified collection.
CreateDocument(ctx context.Context, dbName, collectionName string, document any) (string, error)
// GetDocument retrieves a document by its ID from the specified collection.
GetDocument(ctx context.Context, dbName, collectionName, documentID string, result any) error
// UpdateDocument updates an existing document in the specified collection.
UpdateDocument(ctx context.Context, dbName, collectionName, documentID string, document any) error
// DeleteDocument deletes a document by its ID from the specified collection.
DeleteDocument(ctx context.Context, dbName, collectionName, documentID string) error

// GetEdges retrieves all the edge documents connected to a specific vertex in an ArangoDB graph.
GetEdges(ctx context.Context, dbName, graphName, edgeCollection, vertexID string, resp any) error

// Query executes an AQL query and binds the results
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any) error

HealthCheck(context.Context) (any, error)
}
```

Users can easily inject a driver that supports this interface, providing usability without compromising the extensibility to use multiple databases.

Import the GoFr's external driver for ArangoDB:

```shell
go get gofr.dev/pkg/gofr/datasource/arangodb@latest
```

### Example

```go
package main

import (
"fmt"

"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/arangodb"
)

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
app := gofr.New()

// Configure the ArangoDB client
arangoClient := arangodb.New(arangodb.Config{
Host: "localhost",
User: "root",
Password: "root",
Port: 8529,
})
app.AddArangoDB(arangoClient)

// Example routes demonstrating different types of operations
app.POST("/setup", Setup)
app.POST("/users/{name}", CreateUserHandler)
app.POST("/friends", CreateFriendship)
app.GET("/friends/{collection}/{vertexID}", GetEdgesHandler)

app.Run()
}

// Setup demonstrates database and collection creation
func Setup(ctx *gofr.Context) (interface{}, error) {
_, err := ctx.ArangoDB.CreateDocument(ctx, "social_network", "", nil)
if err != nil {
return nil, fmt.Errorf("failed to create database: %w", err)
}

if err := createCollection(ctx, "social_network", "persons"); err != nil {
return nil, err
}
if err := createCollection(ctx, "social_network", "friendships"); err != nil {
return nil, err
}

// Define and create the graph
edgeDefs := arangodb.EdgeDefinition{
{Collection: "friendships", From: []string{"persons"}, To: []string{"persons"}},
}

_, err = ctx.ArangoDB.CreateDocument(ctx, "social_network", "social_graph", edgeDefs)
if err != nil {
return nil, fmt.Errorf("failed to create graph: %w", err)
}

return "Setup completed successfully", nil
}

// Helper function to create collections
func createCollection(ctx *gofr.Context, dbName, collectionName string) error {
_, err := ctx.ArangoDB.CreateDocument(ctx, dbName, collectionName, nil)
if err != nil {
return fmt.Errorf("failed to create collection %s: %w", collectionName, err)
}
return nil
}

// CreateUserHandler demonstrates user management and document creation
func CreateUserHandler(ctx *gofr.Context) (interface{}, error) {
name := ctx.PathParam("name")

// Create a person document
person := Person{
Name: name,
Age: 25,
}
docID, err := ctx.ArangoDB.CreateDocument(ctx, "social_network", "persons", person)
if err != nil {
return nil, fmt.Errorf("failed to create person document: %w", err)
}

return map[string]string{
"message": "User created successfully",
"docID": docID,
}, nil
}

// CreateFriendship demonstrates edge document creation
func CreateFriendship(ctx *gofr.Context) (interface{}, error) {
var req struct {
From string `json:"from"`
To string `json:"to"`
StartDate string `json:"startDate"`
}

if err := ctx.Bind(&req); err != nil {
return nil, err
}

edgeDocument := map[string]any{
"_from": fmt.Sprintf("persons/%s", req.From),
"_to": fmt.Sprintf("persons/%s", req.To),
"startDate": req.StartDate,
}

// Create an edge document for the friendship
edgeID, err := ctx.ArangoDB.CreateDocument(ctx, "social_network", "friendships", edgeDocument)
if err != nil {
return nil, fmt.Errorf("failed to create friendship: %w", err)
}

return map[string]string{
"message": "Friendship created successfully",
"edgeID": edgeID,
}, nil
}

// GetEdgesHandler demonstrates fetching edges connected to a vertex
func GetEdgesHandler(ctx *gofr.Context) (interface{}, error) {
collection := ctx.PathParam("collection")
vertexID := ctx.PathParam("vertexID")

fullVertexID := fmt.Sprintf("%s/%s", collection, vertexID)

// Prepare a slice to hold edge details
edges := make(arangodb.EdgeDetails, 0)

// Fetch all edges connected to the given vertex
err := ctx.ArangoDB.GetEdges(ctx, "social_network", "social_graph", "friendships",
fullVertexID, &edges)
if err != nil {
return nil, fmt.Errorf("failed to get edges: %w", err)
}

return map[string]interface{}{
"vertexID": vertexID,
"edges": edges,
}, nil
}
```


1 change: 1 addition & 0 deletions pkg/gofr/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Container struct {
OpenTSDB OpenTSDB
ScyllaDB ScyllaDB
SurrealDB SurrealDB
ArangoDB ArangoDB

KVStore KVStore

Expand Down
26 changes: 26 additions & 0 deletions pkg/gofr/container/datasources.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,29 @@ type ScyllaDBProvider interface {
ScyllaDB
provider
}

type ArangoDB interface {
// CreateDocument creates a new document in the specified collection.
CreateDocument(ctx context.Context, dbName, collectionName string, document any) (string, error)
// GetDocument retrieves a document by its ID from the specified collection.
GetDocument(ctx context.Context, dbName, collectionName, documentID string, result any) error
// UpdateDocument updates an existing document in the specified collection.
UpdateDocument(ctx context.Context, dbName, collectionName, documentID string, document any) error
// DeleteDocument deletes a document by its ID from the specified collection.
DeleteDocument(ctx context.Context, dbName, collectionName, documentID string) error

// GetEdges retrieves all the edge documents connected to a specific vertex in an ArangoDB graph.
GetEdges(ctx context.Context, dbName, graphName, edgeCollection, vertexID string, resp any) error

// Query executes an AQL query and binds the results
Query(ctx context.Context, dbName string, query string, bindVars map[string]any, result any) error

HealthChecker
}

// ArangoDBProvider is an interface that extends ArangoDB with additional methods for logging, metrics, and connection management.
type ArangoDBProvider interface {
ArangoDB

provider
}
3 changes: 3 additions & 0 deletions pkg/gofr/container/mock_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ func NewMockContainer(t *testing.T, options ...options) (*Container, *Mocks) {
opentsdbMock := NewMockOpenTSDBProvider(ctrl)
container.OpenTSDB = opentsdbMock

arangoMock := NewMockArangoDBProvider(ctrl)
container.ArangoDB = arangoMock

var httpMock *service.MockHTTP

container.Services = make(map[string]service.HTTP)
Expand Down
Loading

0 comments on commit cd52265

Please sign in to comment.