Patterns for designing flexible architecture in node.js (CQRS/ES/Onion)

“flexible” —a free stock photo I found which makes this blog post much nicer and artistic.

“flexible” how?

  • separate core business logic from implementation details
  • be independent of any database, framework or service
  • use simple pure functions whenever possible
  • make the project easy to scale “horizontally”
  • make the project easy to test
  • use type system primarily to communicate the “ubiquitous language” of the core domain

Project details

  • article — a submission (like a blog post or a youtube video) an author is promoting
  • journal — collection of articles which are accepted only if a set of rules defined by the journal is satisfied
  • user — an author or a peer reviewer with a rank that grants them some privileges (similar to StackOverflow’s ranking system)

Every action causes a reaction

every action causes a reaction
domain events for each aggregate
“JournalEvent” defined as union of objects using TypeScript
  • write side (or a command side) —deals with issues of event storage, ensuring business rules and handling commands
  • read side (or a query side) —takes events produced by the write side and uses them to build and maintain a model that is suitable for answering the client’s queries
CQRS Separates Queries and Commands (source)

Mental model for storing events

There are multiple event lists (event streams) and each one contains events that correspond to a different aggregate

Conceptual example of a journal event streams

The big picture

“the big picture” — components of the write side
“the big picture” — Onion architecture
  • At the heart of the architecture is a domain model (containing business rules), implemented using only pure functions (easy to test)
  • Command handlers can use a domain model and communicate with the outside world only by using injected repository which implements repository interface (easy to mock)
  • The outermost layer has access to all inner layers. It provides the implementation of repository interfaces, entrypoints to the system (REST API), connection to the database (Event store) and similar.
directory structure

Authentication & formation of a command

CreateJournal => JournalCreated
AddJournalEditor => JournalEditorAdded
ConfirmJournalEditor => JournalEdditorConfirmed
...
ReviewArticle => [ArticleReviewed, ArticlePromoted, ArticleAccepted]
ReviewArticle => [ArticleReviewed, ArticleRejected]
{
name: 'AddJournalEditor',
payload: {
journalId: 'journal-1',
editorInfo: {
email: 'editor@gmail.com'
},
timestamp: 1511865224832
}
}
{
userId: 'xyz',
payload: {
journalId: 'journal-1',
editorInfo: {
email: 'editor@gmail.com'
},
timestamp: 1511865224832
}
}
Express “command” route

Command handler — validating the input data

  1. Validate the command on its own merits
  2. Validate the command on the current state of the aggregate
  3. If validation is successful, attempt to persist new events. If there’s a concurrency conflict during this step, either give up or retry things
Command types defined with “io-ts”

Using a domain model to check business rules

AddJournalEditor command handler using addEditor function
“addEditor” function as part of the journal aggregate
TypeScript errors

Retrieving the current state of the aggregate with a repository

AddJournalEditor command handler using user and journal repositories
Type of Journal aggregate state
Conceptual example of a journal event streams
“reduceToJournal” function

Saving events in the Event Store

AddJournalEditor command handler attempting to save new events (simpler version)

Summary of the application flow on the write side

“the big picture” — components of the write side
  1. Command is an object sent by the user (from the UI)
  2. REST API receives a command and handles user authentication
  3. “authenticated command” is then sent to a command handler
  4. Command handler makes a request to a repository for the aggregate state
  5. Repository retrieves events from the event store and transforms them to an aggregate state using a reducer defined in a domain model
  6. Command handler validates the command on the current state of the aggregate using a domain model which responds with resulting events
  7. Command handler sends resulting events to a repository
  8. Repository attempts to persist received data in the event store while ensuring consistency using optimistic locking

The Read Side

If your your hosting bill is unjustifiably YUGE! mostly due to complex queries - you should consider CQRS/ES architecture.

Conclusion

  • use a similar model where events are replaced with objects persisted in a NoSQL database (no event sourcing)
  • use reducers from a write model for client queries (no CQRS)
  • use onion architecture to build #serverless applications (with “lamdas” or “cloud functions”) more easily (by mocking infrastructure layer in “development stage”)
  • use types in a similar fashion where a domain is presented in a fine-grained, self-documenting way (type-first development)
  • use a runtime type system for IO validation (like io-ts)

Resources

  • Martin Fowler — Event Sourcing (video, article)
  • Martin Fowler — CQRS (article)
  • Greg Young — Event Sourcing (video)
  • Alberto Brandolini — Event Storming (article)
  • Chris Richardson — Developing microservices with aggregates (video)
  • Scott Millett — Patterns, Principles, and Practices of DDD (book)
  • CQRS.nu — FAQ (article)
  • MSDN — Introducing Event Sourcing (article)
  • Scott Wlaschin — Domain Driven Design (video, article)
  • Mark Seemann — Functional architecture is Ports and Adapters (article, video)

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

A Unique Software Engineer Technical Interview Question

Emergent behaviour and dashboarding in Instana

Puzzle #28: can you find the number?

A quick guide to Backlog Refinement

Letting your engineers choose what to build

An IT Graduate’s frustration with a Fake ‘Senior Test Automation Engineer’

Common notes about public, private and protected methods in ruby

Big-O Notation

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dom Kriskovic

Dom Kriskovic

More from Medium

NestJS: The Why’s

Structure of a NestJS boilerplate on installation

Why you should start using Nest JS in 2022?

Repository pattern in Nest.js

NestJS User Injection