dustinmoris
dustinmoris

Reputation: 3361

Put into Google Cloud Datastore fails for interface entity

I'm in the process of learning some basic concepts in Go and as such I'm experimenting with a data layer abstraction similar to what I would have done in other programming languages and I am running in the following error with my below code:

Failed to save task: datastore: invalid entity type

The code is the following:

package main

import (
    "context"
    "fmt"
    "log"

    "cloud.google.com/go/datastore"
    "github.com/google/uuid"
)

type DatastoreEntity interface {
    Kind() string
    Name() string
}

type Task struct {
    TaskId      string
    Description string
}

func (task *Task) Kind() string {
    return "tasks"
}

func (task *Task) Name() string {
    return task.TaskId
}

func main() {
    task := Task{
        TaskId:      uuid.New().String(),
        Description: "Buy milk",
    }
    SaveEntity(&task)
}

func SaveEntity(entity DatastoreEntity) {
    ctx := context.Background()
    projectId := "my-gcp-project"
    client, err := datastore.NewClient(ctx, projectId)
    if err != nil {
        log.Fatalf("Failed to create client: %v", err)
    }

    entityKey := datastore.NameKey(entity.Kind(), entity.Name(), nil)

    if _, err := client.Put(ctx, entityKey, &entity); err != nil {
        log.Fatalf("Failed to save task: %v", err)
    }

    fmt.Printf("Saved %v: %v\n", entityKey, entity.Name())
}

Any help in explaining me why this doesn't work would be much appreciated.

My second question is that in the official datastore Go package documentation it says the following:

// Create a datastore client. In a typical application, you would create
// a single client which is reused for every datastore operation.
dsClient, err := datastore.NewClient(ctx, "my-project")
if err != nil {
    // Handle error.
}

What is the recommended pattern to instantiate a dsClient only once in an application?

Upvotes: 1

Views: 259

Answers (1)

icza
icza

Reputation: 417452

The doc is pretty clear on Client.Put():

Put saves the entity src into the datastore with the given key. src must be a struct pointer or implement PropertyLoadSaver.

You don't pass a struct pointer nor PropertyLoadSaver. You pass a pointer to interface type (which you should rarely if ever use). In Go, interface is kind of like a "wrapper" type, it may wrap a concrete value and its type, where wrapped value may be a pointer too. So if a pointer is needed, the pointer is wrapped in the interface, so it's unneeded to use a pointer to the interface itself. In some cases it's still required or useful, but it's rare. Until you bump into a need for it, avoid it. An example may be when you need the reflect type descriptor of the interface type, see how to append nil to dynamic type slice by reflect.Append.

Since you use *Task as your entity (which is a pointer to struct), simply use entity instead of &entity.

client.Put(ctx, entityKey, entity)

What is the recommended pattern to instantiate a dsClient only once in an application?

One way to do it would be to use a package level ("global") variable where you store a datastore.Client created once. Wherever you need a Client, just refer to this variable.

Upvotes: 3

Related Questions