Skip to content

Upgrading to v0.9

goes v0.9.0 migrates the MongoDB backend to version 2 of the official MongoDB Go driver and deprecates the natsjs backend.

text
go.mongodb.org/mongo-driver  →  go.mongodb.org/mongo-driver/v2

No data migration is required. The MongoDB backend keeps its database names, collections, indexes, and stored BSON fields — existing data remains fully usable after the upgrade.

That storage compatibility does not make application-defined BSON codecs source-compatible. Custom value codec method signatures must still be updated as described below.

Who Is Affected?

If you...Then...
don't use the MongoDB or natsjs backendupgrade goes — nothing else to do
use the MongoDB backend only through goes options like mongo.URL(...)upgrade goes and run go mod tidy
exchange MongoDB driver types with goes — clients, options, ObjectIDs, sessions, collectionsmigrate your driver usage — see below
implement MarshalBSONValue or UnmarshalBSONValueupdate the method signatures and verify the stored BSON type — see below
use the natsjs backendplan a move to a supported event store — see natsjs deprecation

Let an AI agent do it

This migration is well-suited for an AI coding agent. Give the agent this page and the MongoDB migration guide linked below, then ask it to update the driver dependency and affected APIs, search for custom BSON codec implementations, run go mod tidy, compile all code behind the mongo build tag, run the test suite, and review the resulting diff.

MongoDB: Migrating to Driver v2

MongoDB maintains a comprehensive Go Driver 2.0 migration guide. The sections below cover the changes most relevant to goes applications.

1. Upgrade the modules and imports

bash
go get github.com/modernice/goes@v0.9.0 go.mongodb.org/mongo-driver/v2@v2.8.0
go mod tidy

Add /v2 to every driver import:

go
// Before
import "go.mongodb.org/mongo-driver/mongo"

// After
import "go.mongodb.org/mongo-driver/v2/mongo"

The same applies to packages such as bson, mongo/options, and mongo/readpref.

2. Update direct mongo.Connect calls

mongo.Connect no longer accepts a context.Context:

go
// Before
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))

// After
client, err := mongo.Connect(options.Client().ApplyURI(uri))

This only affects direct calls to the driver. The backend's (*EventStore).Connect(ctx, opts...) and (*SnapshotStore).Connect(ctx) methods still accept a context.

3. Replace primitive.ObjectID with bson.ObjectID

The former bson/primitive package was merged into bson:

go
// Before
import "go.mongodb.org/mongo-driver/bson/primitive"

id := primitive.NewObjectID()
var modelID primitive.ObjectID

// After
import "go.mongodb.org/mongo-driver/v2/bson"

id := bson.NewObjectID()
var modelID bson.ObjectID

This is especially relevant when bson.ObjectID is the ID type of a model repository.

4. Update custom BSON value codecs

Driver v2 changed the bson.ValueMarshaler and bson.ValueUnmarshaler interfaces to represent the BSON type as a byte:

go
type ValueMarshaler interface {
	MarshalBSONValue() (typ byte, data []byte, err error)
}

type ValueUnmarshaler interface {
	UnmarshalBSONValue(typ byte, data []byte) error
}

In v1 these methods used bsontype.Type. Change the signatures to byte, then convert at calls that still use bson.Type:

go
func (v Value) MarshalBSONValue() (byte, []byte, error) {
	typ, data, err := bson.MarshalValue(v.String())
	return byte(typ), data, err
}

func (v *Value) UnmarshalBSONValue(typ byte, data []byte) error {
	return bson.UnmarshalValue(bson.Type(typ), data, &v.value)
}

WARNING

Do not use bson.Type in these method signatures. Although its underlying type is byte, it is a distinct named type, so the methods no longer implement the driver interfaces. Code can still compile while the driver silently falls back to its default encoding or decoding behavior.

Add compile-time assertions so future driver upgrades cannot silently disable the custom codec:

go
var (
	_ bson.ValueMarshaler   = Value{}
	_ bson.ValueUnmarshaler = (*Value)(nil)
)

Test both the exact stored BSON type and decoding data in its existing on-disk shape. A round-trip test alone can miss this regression when the default codec can encode and decode the same unintended representation.

5. Update sessions and transaction callbacks

In v2, mongo.Session is a struct used through *mongo.Session, and session callbacks receive a standard context.Context. Retrieve the session with mongo.SessionFromContext:

go
err := client.UseSession(ctx, func(ctx context.Context) error {
	session := mongo.SessionFromContext(ctx)

	if err := session.StartTransaction(); err != nil {
		return err
	}

	// Use ctx for operations that belong to the session.
	return session.CommitTransaction(ctx)
})

Accordingly, Transaction.Session() from the MongoDB backend now returns *mongo.Session. Transaction hooks still receive mongo.TransactionContext.

6. Update query option interceptors

Driver v2 represents operation options as builders. A WithQueryOptions callback now receives and returns *options.FindOptionsBuilder:

go
store := mongo.NewEventStore(enc,
	mongo.WithQueryOptions(func(opts *options.FindOptionsBuilder) *options.FindOptionsBuilder {
		return opts.SetBatchSize(500)
	}),
)

Code that only constructs options with calls such as options.Find().SetLimit(...) keeps the same shape. Code that reads option fields must first apply the builder's setters to the corresponding options value.

7. Decode Distinct results when using the driver directly

Collection.Distinct now returns a result that must be decoded:

go
var names []string
if err := collection.Distinct(ctx, "name", bson.D{}).Decode(&names); err != nil {
	return err
}

Checklist

  • Replace every go.mongodb.org/mongo-driver/... import with go.mongodb.org/mongo-driver/v2/....
  • Remove the context argument from direct mongo.Connect calls.
  • Replace primitive.ObjectID and primitive.NewObjectID with their bson equivalents.
  • Update every bson.ValueMarshaler and bson.ValueUnmarshaler implementation to use byte, add compile-time interface assertions, and verify the stored BSON type and decoding of existing data.
  • Change stored or returned sessions to *mongo.Session.
  • Replace mongo.SessionContext callback parameters with context.Context and use mongo.SessionFromContext.
  • Update WithQueryOptions callbacks to use *options.FindOptionsBuilder.
  • Run go mod tidy, then compile and test code behind the mongo build tag.

natsjs Deprecation

The natsjs backend (the JetStream-backed event store) is deprecated in v0.9 and will be removed in a future release. It has architectural flaws that risk data corruption and silent event loss: missing optimistic concurrency control, non-atomic writes, and process-local stream discovery that leaves subscribers blind to aggregate types first created by other services and makes unfiltered queries return incomplete results.

It also scales poorly in larger systems: broker memory grows with the total number of stored events, every insert costs two synchronous round trips, and every aggregate load re-reads the aggregate's full history — snapshots do not reduce the amount of data fetched.

WARNING

Do not start new projects on the natsjs backend. Existing projects should plan a migration.

If you use it:

  • Use the MongoDB or PostgreSQL backend as the event store.
  • Keep using the core NATS backend as the event bus — it is unaffected.

In v0.9 the deprecation is compile-time only: existing code keeps working, but deprecation notices appear in your editor and in linters such as staticcheck. See the natsjs README for the full list of issues.