Skip to content

Commit

Permalink
feat(aggregate): automatically add query.Name() option (closes #144)
Browse files Browse the repository at this point in the history
feat(typed.go): add 'name' field to TypedRepository to store aggregate name
feat(typed.go): modify Query method to include aggregate name in query
feat(typed.go): update Use method to save aggregate after applying function
These changes improve the TypedRepository's functionality and readability.
  • Loading branch information
bounoable committed Sep 20, 2023
1 parent 5d9a40b commit 5f0ca91
Showing 1 changed file with 61 additions and 30 deletions.
91 changes: 61 additions & 30 deletions aggregate/repository/typed.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,98 @@ import (

"github.com/google/uuid"
"github.com/modernice/goes/aggregate"
"github.com/modernice/goes/aggregate/query"
"github.com/modernice/goes/helper/pick"
"golang.org/x/exp/slices"
)

// TypedRepository is a wrapper around aggregate.Repository that works with
// specific typed aggregates. It provides methods to save, fetch, query, and
// delete typed aggregates and utilizes an aggregate factory function to
// instantiate aggregates when needed.
// TypedRepository is a specialized version of an aggregate repository that
// deals with a specific type of aggregate. It provides methods for saving,
// fetching, querying and deleting aggregates of a certain type. The type of the
// aggregate this repository works with is determined by the factory function
// passed during the creation of the TypedRepository instance. It also provides
// access to the underlying generic repository and the factory function used to
// create new instances of the aggregate. The provided context is used for
// cancellation and timeout handling.
type TypedRepository[Aggregate aggregate.TypedAggregate] struct {
repo aggregate.Repository
make func(uuid.UUID) Aggregate
name string
}

// Typed returns a new TypedRepository for the provided aggregate.Repository and
// makeFunc. The makeFunc is used to create new instances of the typed Aggregate
// with a given UUID when fetching them from the underlying
// aggregate.Repository.
// Typed constructs a new TypedRepository for the provided aggregate.Repository
// and makeFunc. The makeFunc is used to create new instances of Aggregate. The
// returned TypedRepository only operates on Aggregates of the type created by
// makeFunc.
func Typed[Aggregate aggregate.TypedAggregate](r aggregate.Repository, makeFunc func(uuid.UUID) Aggregate) *TypedRepository[Aggregate] {
return &TypedRepository[Aggregate]{repo: r, make: makeFunc}
return &TypedRepository[Aggregate]{
repo: r,
make: makeFunc,
name: pick.AggregateName(makeFunc(uuid.Nil)),
}
}

// NewOf creates a new TypedRepository for the specified
// aggregate.TypedAggregate using the provided aggregate.Repository and factory
// function to instantiate Aggregates with a given UUID.
// NewOf creates a new instance of a TypedRepository for the specified Aggregate
// type. The provided aggregate.Repository and make function are used to
// construct the TypedRepository. The make function is used to create new
// instances of the Aggregate when needed.
func NewOf[Aggregate aggregate.TypedAggregate](r aggregate.Repository, makeFunc func(uuid.UUID) Aggregate) *TypedRepository[Aggregate] {
return Typed(r, makeFunc)
}

// Repository returns the underlying aggregate.Repository of the
// TypedRepository.
// TypedRepository. This allows direct access to the repository that stores and
// retrieves the aggregates.
func (r *TypedRepository[Aggregate]) Repository() aggregate.Repository {
return r.repo
}

// NewFunc returns the function that creates a new typed Aggregate with the
// given UUID. This function is used by the TypedRepository to instantiate
// Aggregates when fetching them from the underlying aggregate.Repository.
// NewFunc returns a function that creates a new instance of the Aggregate type.
// The returned function takes a UUID as an argument and returns an Aggregate.
// The UUID is used to identify the new Aggregate instance.
func (r *TypedRepository[Aggregate]) NewFunc() func(uuid.UUID) Aggregate {
return r.make
}

// Save stores the given Aggregate in the repository and returns an error if the
// operation fails.
// Save stores the provided aggregate into the repository. It takes a context
// for cancellation and deadline purposes and the aggregate to be stored.
// Returns an error if the saving process encounters any issues.
func (r *TypedRepository[Aggregate]) Save(ctx context.Context, a Aggregate) error {
return r.repo.Save(ctx, a)
}

// Fetch retrieves the latest version of an Aggregate with the given UUID from
// the repository, returning the fetched Aggregate and any error encountered.
// Fetch retrieves the aggregate of the specified type and identifier from the
// repository. It returns the fetched aggregate and any error encountered during
// the process. If the aggregate does not exist, Fetch returns an error.
func (r *TypedRepository[Aggregate]) Fetch(ctx context.Context, id uuid.UUID) (Aggregate, error) {
out := r.make(id)
return out, r.repo.Fetch(ctx, out)
}

// FetchVersion retrieves an Aggregate with the specified ID and version from
// the TypedRepository, returning an error if it cannot be fetched.
// FetchVersion retrieves a specific version of the aggregate identified by its
// UUID from the repository. The function returns an error if the requested
// version does not exist or if there is an issue fetching it from the
// repository.
func (r *TypedRepository[Aggregate]) FetchVersion(ctx context.Context, id uuid.UUID, version int) (Aggregate, error) {
out := r.make(id)
return out, r.repo.FetchVersion(ctx, out, version)
}

// Query retrieves Aggregates from the underlying aggregate.Repository that
// match the specified aggregate.Query. It returns channels for receiving the
// Aggregates and any errors encountered during the query, as well as an error
// if the query itself cannot be executed.
// Query returns a channel of Aggregates and a channel of errors found during
// the query execution. The Aggregates are retrieved from the underlying
// repository and are of the type that the TypedRepository is configured for.
// The returned Aggregates are created with the factory function provided during
// the construction of the TypedRepository. If an Aggregate is not of the
// expected type, it's skipped. The query execution can be cancelled through the
// provided context. The function also returns an error if there's an issue
// executing the query on the underlying repository.
func (r *TypedRepository[Aggregate]) Query(ctx context.Context, q aggregate.Query) (<-chan Aggregate, <-chan error, error) {
qry := query.Expand(q)
if !slices.Contains(qry.Names(), r.name) {
qry.Q.Names = append(qry.Q.Names, r.name)
}
q = qry

str, errs, err := r.repo.Query(ctx, q)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -100,15 +127,19 @@ func (r *TypedRepository[Aggregate]) Query(ctx context.Context, q aggregate.Quer
return out, errs, nil
}

// Use retrieves an Aggregate with the specified ID from the TypedRepository,
// applies the given function to it, and returns any error encountered during
// this process.
// Use retrieves an [Aggregate] with the provided UUID, applies the function fn
// to it, and then saves the [Aggregate] back into the repository. If fn returns
// an error, the [Aggregate] is not saved and the error is returned. The
// operation is performed in a context, and its execution may be cancelled if
// the context is closed.
func (r *TypedRepository[Aggregate]) Use(ctx context.Context, id uuid.UUID, fn func(Aggregate) error) error {
a := r.make(id)
return r.repo.Use(ctx, a, func() error { return fn(a) })
}

// Delete removes the specified Aggregate from the repository.
// Delete removes the specified Aggregate from the TypedRepository. It operates
// within the context passed to it. If any error occurs during the deletion, it
// is returned for handling.
func (r *TypedRepository[Aggregate]) Delete(ctx context.Context, a Aggregate) error {
return r.repo.Delete(ctx, a)
}

0 comments on commit 5f0ca91

Please sign in to comment.