Skip to content

Getting Started with Go

This guide walks you through building a complete EvidentSource application using the Go SDK.

  • Go 1.22 or later
  • TinyGo 0.40.1 or later (for WASM components)
  • An EvidentSource server instance (local or cloud)

The fastest way to get started is using the project template:

Terminal window
# Clone the SDKs repository
git clone https://github.com/evidentsystems/evidentsource-sdks
cd evidentsource-sdks/go/packages/evidentsource-functions/templates
# Create a new project
./create-project.sh my-app my-database open-account active-accounts

This creates a complete project with:

  • domain/ - Shared types for events and commands
  • state_changes/ - Example state change component
  • state_views/ - Example state view component
  • build_all.sh - Build all WASM components with TinyGo
  • install.sh - Deploy components to EvidentSource
  • CLAUDE.md - AI assistant context for development
Terminal window
cd my-app
./build_all.sh # Build WASM components
./install.sh --all # Deploy to server

See the Template Guide for full documentation.

If you prefer to set up manually or need a client-only application:

Terminal window
mkdir my-banking-app
cd my-banking-app
go mod init github.com/myorg/banking
Terminal window
go get github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-core
go get github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-client
go get github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-functions

Create main.go:

package main
import (
"context"
"fmt"
"log"
"github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-client"
"github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-core/domain"
)
func main() {
ctx := context.Background()
// Connect to the server
es, err := client.ConnectSimple(ctx, "http://localhost:50051")

For production deployments, you’ll need to authenticate with a JWT token. See Authentication for details on the trust model.

import "os"
func main() {
ctx := context.Background()
// Bearer token authentication (production)
token := os.Getenv("EVS_TOKEN")
creds := client.BearerTokenCredentials{Token: token}
es, err := client.Connect(ctx, "api.example.com:50051", creds)
// DevMode for local development (no auth required)
es, err := client.ConnectSimple(ctx, "http://localhost:50051")
if err != nil {
log.Fatal(err)
}
defer es.Close()
// Create or connect to a database
dbName, _ := domain.NewDatabaseName("banking")
conn, err := es.ConnectDatabase(ctx, dbName)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Connected to database at revision %d\n", conn.Revision())
// Execute a state change
scName, _ := domain.NewStateChangeName("open-account")
db, err := conn.ExecuteStateChange(ctx, scName, 1, client.JSONCommandRequest(map[string]any{
"accountId": "acct-001",
"customerName": "Alice Smith",
"initialDeposit": 1000.00,
}))
if err != nil {
log.Fatal(err)
}
fmt.Printf("New revision: %d\n", db.Revision())
// Query a state view
svName, _ := domain.NewStateViewName("account-summary")
content, err := db.ViewState(ctx, svName, 1, map[string]string{
"account_id": "acct-001",
})
if err != nil {
log.Fatal(err)
}
if content != nil {
fmt.Printf("Account data: %s\n", string(content.Content))
}
}

State changes handle commands and emit events. Create the directory structure:

Terminal window
mkdir -p state-changes/open-account

Create state-changes/open-account/main.go:

package main
import (
"encoding/json"
coredomain "github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-core/domain"
sdk "github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-functions"
"github.com/google/uuid"
)
// Command
type OpenAccountCommand struct {
AccountID string `json:"accountId"`
CustomerName string `json:"customerName"`
InitialDeposit float64 `json:"initialDeposit"`
}
// Events
type AccountOpened struct {
AccountID string `json:"accountId"`
CustomerName string `json:"customerName"`
}
type AccountCredited struct {
Amount float64 `json:"amount"`
Description string `json:"description"`
}
func decide(db sdk.DatabaseAccess, cmd OpenAccountCommand, meta sdk.StateChangeMetadata) (sdk.DecideResult, error) {
// Validate
if cmd.CustomerName == "" {
return sdk.DecideResult{}, sdk.ValidationErr("customer name is required")
}
if cmd.InitialDeposit < 0 {
return sdk.DecideResult{}, sdk.ValidationErr("initial deposit cannot be negative")
}
// Check if account already exists
existing, _ := db.ViewState("account-summary", 1, map[string]string{
"account_id": cmd.AccountID,
})
if existing != nil {
return sdk.DecideResult{}, sdk.ConflictErr("account already exists")
}
var events []coredomain.ProspectiveEvent
// Create AccountOpened event
openedData, _ := json.Marshal(AccountOpened{
AccountID: cmd.AccountID,
CustomerName: cmd.CustomerName,
})
events = append(events, coredomain.ProspectiveEvent{
ID: uuid.New().String(),
Stream: "accounts",
EventType: "com.banking.account.opened",
Subject: &cmd.AccountID,
Data: coredomain.StringEventData{Data: string(openedData)},
DataContentType: ptr("application/json"),
})
// Create initial deposit event if non-zero
if cmd.InitialDeposit > 0 {
creditData, _ := json.Marshal(AccountCredited{
Amount: cmd.InitialDeposit,
Description: "Initial deposit",
})
events = append(events, coredomain.ProspectiveEvent{
ID: uuid.New().String(),
Stream: "accounts",
EventType: "com.banking.account.credited",
Subject: &cmd.AccountID,
Data: coredomain.StringEventData{Data: string(creditData)},
DataContentType: ptr("application/json"),
})
}
return sdk.DecideResult{Events: events}, nil
}
func ptr(s string) *string { return &s }
var Adapter = sdk.StateChangeAdapter[OpenAccountCommand]{
ParseCommand: sdk.JSONCommandParser[OpenAccountCommand](),
Decide: decide,
}
func main() {}

Create state-changes/open-account/go.mod:

module github.com/myorg/banking/state-changes/open-account
go 1.22

Build the WASM component:

Terminal window
cd state-changes/open-account
tinygo build -o open-account.wasm -target=wasip2 -scheduler=none .

State views materialize read models from events. Create the directory structure:

Terminal window
mkdir -p state-views/account-summary

Create state-views/account-summary/main.go:

package main
import (
"encoding/json"
sdk "github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-functions"
)
// State
type AccountSummary struct {
ID string `json:"id"`
Name string `json:"name"`
Balance float64 `json:"balance"`
IsOpen bool `json:"isOpen"`
}
// Event data types
type AccountOpenedData struct {
AccountID string `json:"accountId"`
CustomerName string `json:"customerName"`
}
type AccountCreditedData struct {
Amount float64 `json:"amount"`
}
type AccountDebitedData struct {
Amount float64 `json:"amount"`
}
type AccountSummaryView struct{}
func (v *AccountSummaryView) InitialState() AccountSummary {
return AccountSummary{}
}
func (v *AccountSummaryView) EvolveEvent(state AccountSummary, event sdk.Event) AccountSummary {
switch event.EventType {
case "com.banking.account.opened":
var data AccountOpenedData
if err := json.Unmarshal(event.Data, &data); err == nil {
state.ID = data.AccountID
state.Name = data.CustomerName
state.IsOpen = true
}
case "com.banking.account.credited":
var data AccountCreditedData
if err := json.Unmarshal(event.Data, &data); err == nil {
state.Balance += data.Amount
}
case "com.banking.account.debited":
var data AccountDebitedData
if err := json.Unmarshal(event.Data, &data); err == nil {
state.Balance -= data.Amount
}
case "com.banking.account.closed":
state.IsOpen = false
}
return state
}
var Adapter = sdk.StateViewAdapter[AccountSummary]{
View: &AccountSummaryView{},
}
func main() {}

Build the WASM component:

Terminal window
cd state-views/account-summary
tinygo build -o account-summary.wasm -target=wasip2 -scheduler=none .

Deploy your WASM components to the EvidentSource server:

Terminal window
# Create database
curl -X POST http://localhost:8080/api/v1/databases/banking
# Install state change
curl -X POST http://localhost:8080/api/v1/databases/banking/state-changes/open-account/versions/1 \
--data-binary @state-changes/open-account/open-account.wasm \
-H "Content-Type: application/wasm"
# Install state view
curl -X POST http://localhost:8080/api/v1/databases/banking/state-views/account-summary/versions/1 \
--data-binary @state-views/account-summary/account-summary.wasm \
-H "Content-Type: application/wasm"

Use the mock database for unit testing:

package main
import (
"testing"
functionstest "github.com/evidentsystems/evidentsource-sdks/go/packages/evidentsource-functions/testing"
)
func TestOpenAccountSuccess(t *testing.T) {
db := functionstest.NewMockDatabase("test-db").WithRevision(100)
cmd := OpenAccountCommand{
AccountID: "acct-001",
CustomerName: "Alice",
InitialDeposit: 100.0,
}
result, err := decide(db, cmd, sdk.StateChangeMetadata{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result.Events) != 2 {
t.Errorf("expected 2 events, got %d", len(result.Events))
}
}
func TestOpenAccountNegativeDeposit(t *testing.T) {
db := functionstest.NewMockDatabase("test-db")
cmd := OpenAccountCommand{
AccountID: "acct-001",
CustomerName: "Alice",
InitialDeposit: -50.0,
}
_, err := decide(db, cmd, sdk.StateChangeMetadata{})
if err == nil {
t.Error("expected validation error")
}
}