Spacetime Soccer
⌘ Read more
Faucet
⌘ Read more
Paper Title
⌘ Read more
What If 2 Gift Guide
⌘ Read more
Change in Slope
⌘ Read more
Account Problems
⌘ Read more
Feature Comparison
⌘ Read more
Feature Comparison
⌘ Read more
Bad Date
⌘ Read more
Y2K and 2038
⌘ Read more
Precision vs Accuracy
⌘ Read more
Soil
⌘ Read more
Königsberg
⌘ Read more
Wirecutter Recommendation
⌘ Read more
Interior Decorating
⌘ Read more
I was inclined to let this go so as not to stir anything up, but after some additional thought I’ve decided to call it out. This twt:

is exactly the kind of ad hominem garbage I came to expect from Twitter™, and I’m disappointed to see it replicated here. Rummaging through someone’s background trying to find a “gotcha” argument to take credibility away from what a person is saying, instead of engaging the ideas directly, is what trolls and bad faith actors do. That’s what the twt above does (falsely, I might add–what’s being claimed is untrue).
If you take issue with something I’ve said, you can mute me, unfollow me, ignore me, use TamperMonkey to turn all my twts into gibberish, engage the ideas directly, etc etc etc. There are plenty of options to make what I said go away. Reading through my links, reading about my organization’s CEO’s background, and trying to use that against me somehow (after misinterpreting it no less)? Besides being unacceptable in a rational discussion, and besides being completely ineffective in stopping me from expressing whatever it is you didn’t like, it’s creepy. Don’t do that.
Encryption
⌘ Read more
Cool S
⌘ Read more
Fermat’s First Theorem
⌘ Read more
Bubble Universes
⌘ Read more
Division Notation
⌘ Read more
Space Adventure
⌘ Read more
2045
⌘ Read more
Road Space Comparison
⌘ Read more
Fan Theories
⌘ Read more
Easy Or Hard
⌘ Read more
Archimedes Principle
⌘ Read more
Battery Life
⌘ Read more
Quantified Self
⌘ Read more
Wing Lift
⌘ Read more
Two Key System
⌘ Read more
Historical Dates
⌘ Read more
Pilot Priority List
⌘ Read more
Everyday Carry
⌘ Read more
Cursed mRNA Cocktail
⌘ Read more
What If? 2 Flowchart
⌘ Read more
Rotation
⌘ Read more
Interruption
⌘ Read more
Things You Should Not Do
⌘ Read more
Artemis Quote
⌘ Read more
First Internet Interaction
⌘ Read more
Universe Price Tiers
⌘ Read more
America Songs
⌘ Read more
Cloud Swirls
⌘ Read more
Tetherball Configurations
⌘ Read more
Physics Safety Tip
⌘ Read more
Age Milestone Privileges
⌘ Read more
Gen Z
⌘ Read more
Unreliable Connection
⌘ Read more
(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 ObjectsA 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
}