Sort by: Newest, Oldest, Most Relevant
(#hn4w24q) @xuu Congratulations! That's by far the longest twt I've seen. :-) Working fine in tt. I was a bit worried, that lengths of multiple screens would be hard to read or scroll. But magically it just scrolled line by line. I reckon two `return`s can be saved in `GetUser(โ€ฆ)`. Oh, on further inspection there are even two nested `err != nil` checks. Generics were desperately needed. I'm glad they finally introduced them. I stumbled across them last week and gave them a shot. The syntax is probably not the very best, but I will get used to it eventually.

matched #3muomba score:11.94 Search by:
Search by 1 mentions:
Search by 1 tags:
(#hn4w24q) (cont.) Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate. ## Domain Objects A domain object can be used as an aggregate by adding the `event.AggregateRoot` struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method. ``` type User struct { Identity string ```json:"identity"` CreatedAt time.Time event.AggregateRoot } // StreamID for the aggregate when stored or loaded from ES. func (a *User) StreamID() string { return "user-" + a.Identity } // ApplyEvent to the aggregate state. func (a *User) ApplyEvent(lis ...event.Event) { for _, e := range lis { switch e := e.(type) { case *UserCreated: a.Identity = e.Identity a.CreatedAt = e.EventMeta().CreatedDate /* ... */ } } } ``` ## Events Events are applied to the aggregate. They are defined by adding the `event.Meta` and implementing the getter/setters for `event.Event` ``` type UserCreated struct { eventMeta event.Meta Identity string } func (c *UserCreated) EventMeta() (m event.Meta) { if c != nil { m = c.eventMeta } return m } func (c *UserCreated) SetEventMeta(m event.Meta) { if c != nil { c.eventMeta = m } } ``` ## Reading Events from EventStore With a domain object that implements the `event.Aggregate` the event store client can load events and apply them using the `Load(ctx, agg)` method. ``` // GetUser populates an user from event store. func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) { user := &domain.User{Identity: userID} err := rw.es.Load(ctx, user) if err != nil { if err != nil { if errors.Is(err, eventstore.ErrStreamNotFound) { return user, ErrNotFound } return user, err } return nil, err } return user, err } ``` ## OnX Commands An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error. ``` // OnCreate raises an UserCreated event to create the user. // Note: The handler will check that the user does not already exsist. func (a *User) OnCreate(identity string) error { event.Raise(a, &UserCreated{Identity: identity}) return nil } // OnScored will attempt to score a task. // If the task is not in a Created state it will fail. func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error { if a.State != TaskStateCreated { return fmt.Errorf("task expected created, got %s", a.State) } event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score}) return nil } ``` ## Crud Operations for OnX Commands The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function. ``` // Create is used when the stream does not yet exist. func (rw *User) Create( ctx context.Context, identity string, fn func(*domain.User) error, ) (*domain.User, error) { session, err := rw.GetUser(ctx, identity) if err != nil && !errors.Is(err, ErrNotFound) { return nil, err } if err = fn(session); err != nil { return nil, err } _, err = rw.es.Save(ctx, session) return session, err } // Update is used when the stream already exists. func (rw *User) Update( ctx context.Context, identity string, fn func(*domain.User) error, ) (*domain.User, error) { session, err := rw.GetUser(ctx, identity) if err != nil { return nil, err } if err = fn(session); err != nil { return nil, err } _, err = rw.es.Save(ctx, session) return session, err } ```

matched #awgtu3q score:11.94 Search by:
Search by 1 tags:
(#hn4w24q) (cont.) Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate. ## Domain Objects A domain object can be used as an aggregate by adding the `event.AggregateRoot` struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method. ``` type User struct { Identity string ```json:"identity"` CreatedAt time.Time event.AggregateRoot } // StreamID for the aggregate when stored or loaded from ES. func (a *User) StreamID() string { return "user-" + a.Identity } // ApplyEvent to the aggregate state. func (a *User) ApplyEvent(lis ...event.Event) { for _, e := range lis { switch e := e.(type) { case *UserCreated: a.Identity = e.Identity a.CreatedAt = e.EventMeta().CreatedDate /* ... */ } } } ``` ## Events Events are applied to the aggregate. They are defined by adding the `event.Meta` and implementing the getter/setters for `event.Event` ``` type UserCreated struct { eventMeta event.Meta Identity string } func (c *UserCreated) EventMeta() (m event.Meta) { if c != nil { m = c.eventMeta } return m } func (c *UserCreated) SetEventMeta(m event.Meta) { if c != nil { c.eventMeta = m } } ``` ## Reading Events from EventStore With a domain object that implements the `event.Aggregate` the event store client can load events and apply them using the `Load(ctx, agg)` method. ``` // GetUser populates an user from event store. func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) { user := &domain.User{Identity: userID} err := rw.es.Load(ctx, user) if err != nil { if err != nil { if errors.Is(err, eventstore.ErrStreamNotFound) { return user, ErrNotFound } return user, err } return nil, err } return user, err } ``` ## OnX Commands An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error. ``` // OnCreate raises an UserCreated event to create the user. // Note: The handler will check that the user does not already exsist. func (a *User) OnCreate(identity string) error { event.Raise(a, &UserCreated{Identity: identity}) return nil } // OnScored will attempt to score a task. // If the task is not in a Created state it will fail. func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error { if a.State != TaskStateCreated { return fmt.Errorf("task expected created, got %s", a.State) } event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score}) return nil } ``` ## Crud Operations for OnX Commands The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function. ``` // Create is used when the stream does not yet exist. func (rw *User) Create( ctx context.Context, identity string, fn func(*domain.User) error, ) (*domain.User, error) { session, err := rw.GetUser(ctx, identity) if err != nil && !errors.Is(err, ErrNotFound) { return nil, err } if err = fn(session); err != nil { return nil, err } _, err = rw.es.Save(ctx, session) return session, err } // Update is used when the stream already exists. func (rw *User) Update( ctx context.Context, identity string, fn func(*domain.User) error, ) (*domain.User, error) { session, err := rw.GetUser(ctx, identity) if err != nil { return nil, err } if err = fn(session); err != nil { return nil, err } _, err = rw.es.Save(ctx, session) return session, err } ```

matched #onjam6q score:11.94 Search by:
Search by 1 tags:
This is twtxt search engine and crawler. Please contact Support if you have any questions, concerns or feedback!