Damian C. Rossney

@dcrossney@ruby.social
62 Followers
257 Following
459 Posts

I'm a Ruby developer from Westchester, New York. I'm interested in web application and internal infrastructure development. Mostly I like to build simple things that work really well.

#CuriousWanderer
#InveterateLearner
#Father
#Entrepreneur
#WebDeveloper
#SkidmoreGrad
#BootcampGrad
#FormerlyIncarcerated

Writingshttps://rossney.net
Gitlabhttps://gitlab.com/dcr8898
Githubhttps://github.com/dcr8898

#Ruby perf question: What's the fastest way to iterate over segments in an HTTP route? (ex. "/users/:id/postst/:post_id")

I currently .split on "/" and call .each on the array. I thought I could avoid creating an array and perhaps gain speed by "consuming" the string directly. I tried three ways.

* Split a segment at a time.
* Use pointers to index over the string.
* Use String Scanner (regex).

The array is always faster. 🤔

Please boost to reach any big brains too busy to follow me. 😉

@ismasan Finally, the logic for compiling the current state of the object lives in a completely different object: a projection.

The projection receives the history of events from the stream, and knows what account entity methods to call to apply each event.

This all feels weird to me because it feels like the business rules surrounding the account concept in this example are divided among three different objects: a handler, the entity, and the projection.

5/🧵

@ismasan In XState, you define your state machine as a JSON object. This may seem tedious, but the object is easily translated to a diagram--and back to an object. It's even possible to edit the diagram to change the code.

This seems valuable in creating a shared mental model of the state machine, and in evaluating its correctness.

I thought maybe Eventide would justify this approach by treating the account entity as a pure data object, but it contains behavior as well (see `withdraw`).

4/🧵

@ismasan I've been re-reading the Eventide docs (for at least the third time). I came back to this simplified example from the introduction page. http://docs.eventide-project.org/

I wanted to look at it again from the Decide-Evolve-React perspective. The method doesn't have the precise function signature of Decide (command, state) -> event(s).

It does accept a command (with account_id and amount data elements). But it then uses the account_id to obtain the current state from an account "store."

1/🧵

Eventide

Evented Autonomous Components

×

@ismasan In XState, you define your state machine as a JSON object. This may seem tedious, but the object is easily translated to a diagram--and back to an object. It's even possible to edit the diagram to change the code.

This seems valuable in creating a shared mental model of the state machine, and in evaluating its correctness.

I thought maybe Eventide would justify this approach by treating the account entity as a pure data object, but it contains behavior as well (see `withdraw`).

4/🧵

@ismasan Finally, the logic for compiling the current state of the object lives in a completely different object: a projection.

The projection receives the history of events from the stream, and knows what account entity methods to call to apply each event.

This all feels weird to me because it feels like the business rules surrounding the account concept in this example are divided among three different objects: a handler, the entity, and the projection.

5/🧵

@ismasan

* The handler knows (piecemeal) when to change state.

* The entity contains methods that mutate its state.

* The projection is a translation layer that knows what entity methods to call, with what parameters, based on the events it receives.

I had envisioned the entity as a stateful, immutable object that would be instantiated with its current attributes, including state. This object could respond to a given command, including arguments, and reply with the appropriate event(s).

6/🧵

@ismasan Likewise, if an instance of the same object is asked to *apply* an event (including arguments), it would respond with a new instance of itself in the appropriate state, with update attributes.

No one seems to be thinking along those lines, but making sense of three separate business objects is a lot to absorb for me.

I will keep reading . . . 🧐

fin 🧵.

@dcrossney I partially agree with you. I learned a lot by studying Eventide and I think it's a fantastic and criminally overlooked project, but personally I tend to find the prescribed DX less cohesive than I'd like it to be (though I suspect their approach can pay off as the project grows).
@dcrossney To be clear, your idea of an "entity" that encapsulates both command handling and state evolution is actually the norm in most ES implementations I've seen. It's usually called Aggregate Root (from DDD). Here's an example from the Sequent library https://sequent.io/docs/concepts/aggregate-root.html
AggregateRoot

CQRS & event sourcing framework for Ruby.

Sequent - CQRS & event sourcing framework for Ruby
@dcrossney personally I'm ambivalent about that approach, and AFAIK Eventide expressly set out to avoid it, too. They still provide their own class and you're encouraged to add some logic there (ex. that `withdraw` method), but the logic is strictly limited to interrogating and updating the instance's local state. By design those instances don't know anything directly about command and event classes. They really just are small data objects that encapsulate local state to guard state changes.
@dcrossney in most Ruby implementations these entities / aggregates are mutable. I think this generally makes sense because most objects in Ruby are also mutable and you want to play to people's familiarity. Naturally, in most FP implementations entities are immutable and you "evolve" them by returning new copies.
@dcrossney but more in general, I think your comments point to the question of what are these entities actually _for_. Coming from OOP and ORMs it's tempting to think of them as fully-fledged domain concepts such as "User", "ShoppingCart", "Account", etc. And to be fair the common link to DDD Aggregates reinforces the idea that they are complex object graphs that encapsulate entire concepts.
@dcrossney but more recently I'm seeing a shift towards thinking of this as "just enough state needed to guard a single operation". So, less "domain models" and more "tiny, local decision models".
In this regard, AxonIQ have interesting thoughts with their Dynamic Consistency Boundaries. Here's the original blog post series: https://sara.event-thinking.io/2023/04/kill-aggregate-chapter-1-I-am-here-to-kill-the-aggregate.html . Worth a read.
Chapter 1 - I am here to kill the aggregate

The aggregate has always been, for me, one of the most controversial and weakest elements of Domain Driven Design. During my consultancy act...

@dcrossney so, in this approach, there's no such thing as aggregates or entities that encapsulate canonical domain concepts. It's more that every command handler is a small decision-making function (its behaviour defined by the command that they receive and the events that they produce). They only "evolve" enough local state from past events to allow them to make a single, focused decision. That local state might map to an "official" domain concept (eg "Shopping Cart"), ...
@dcrossney ... But it could also be something more transient, ex. "maximum age of users who have bought this particular product". So this state could be encoded as a custom class, but it could also just be a number, an array, or whatever's needed to guard the invariant or business rule protected by that command handler.
@dcrossney finally, and because this local state can be anything that's useful to protect business rules, it can totally be something like a state machine definition, big or small, for example using XState (which I love and have been thinking hard how to integrate with my Ruby library).

@ismasan Thanks again for taking the time to read and respond! 🤓 I will read the sources you gave to understand more.

My concern with this fragmented approach is that it will be more difficult to form and hold a mental model of the life cycle of these concepts in an app.

Being able to convert a state machine to a state chart and back is a huge help in this regard. How will anything like that be possible with such fragmented parts?

@dcrossney I share the concerns! I'm all for cohesive mental models, so this kind of fragmentation could be a problem in that regard. Having said that, I suspect it's still possible to track the lifecycle of a higher-order concept. For example, a "holiday booking" workflow could be composed of multiple smaller decisions where each needs a subset of the data to guard its invariants.
@dcrossney the point in DCB though, is that the events in this workflow don't "belong" to any single domain object. They are "tagged" with identifiers for the different concepts (holiday ID, flight ID, hotel ID, user ID, etc) but you build coherent workflows by combining relevant events for each decision. So it's an events-first instead of an entity-first view of the world. This could make domain modelling more maleable and evolvable, because you can remove or introduce new concepts over time.
@dcrossney but again, I have zero experience with all this :)

@ismasan Me neither, but I'm starting to come around to the event-first view. Taking a good look at Sequent now . . .

Thanks again!