Technical lead and Architect for enterprise-grade cloud based contact center solutions with a career related with Computer & Software Engineering, combining functions of senior software development, IT project management and team leadership.
Professional career of +20 years in full-stack development of web and cloud applications with special interest in soft-realtime, scalable and distributed systems, in particular in the domain of contact center business.
Focused on the technology but having a general interest on all perspectives of product development: understanding business and customer requirements, software design, user experience and testing.
- Co-organizer of Lisbon Domain-Driven Meet-up
- Company: Altitude Software
Sessions Lisbon 2020
Domain Events in depth
Domain Events are used to represent a relevant occurrence to domain experts as a way to make them aware to changes in the system.
Domain events and messaging enable eventual consistency and allow improving performance and scalability between aggregates within the same domain. They allow so by avoiding the need for distributed transactions.
It’s also an easy way to have loose coupling between systems, thus facilitating the integration of autonomous systems. By using domain events it’s possible to implement reactive processes using sagas instead of using batch processing using polling and complex queries.
We can think of a maturity spectrum regarding the adoption of Domains Events: from not using events at all; then using internally, inside a process or server to decouple side effects; then exposing events externally like a transaction changelog but not expressing relevant Domain semantics; to the ultimate usage of CQRS and Event Sourcing where Domain Events are the Single Source of Truth. Then current system state is built upon replaying the sequence of stored events.
Modelling events implies using some rules like naming according to the Ubiquitous Language of the Bounded Context.
Publishing domain events should be decoupled from the infrastructural implementation by using design patterns like the Observer / Publish-Subscribe.
Domain Events can be used to implement side effects of changes within your domain across multiple aggregates and can be propagated by using messaging systems such as Apache Kafka or RabbitMQ.
Event Stores can be used as a repository of the domain events, offering a multitude of advantages like being used as a queue for publishing events through a messaging infrastructure, or to have a historical record of every command execution, that can be used for auditing, debugging or for business analytics.
With Event Stores it’s also possible to re-create aggregates by replaying the events, a concept known as Event Sourcing.
By using messaging systems to propagate domain events there is a need to guarantee that a message is handled exactly once by a subscriber or that the handling operation is idempotent, which might require to implement a message de-duplication mechanism.
After this session the attendees should have learned:
- What are Domain Events, when to use them and why you should use them
- How to model events as objects
- How to use a lightweight Publish-Subscriber pattern in a model
- Which components are publishers and which are subscribers
- When an Event Store should be used, how it can be developed
- How to publish outside a Bounded Context across one or multiple aggregates by using Messaging middleware
- How to de-duplicate event messages in a messaging consumer application
The actor model and distributed programming
Today, software systems development needs a new programming model, one that enables distributed computing with highly parallel processing and high performance networks.
Tradicional programming models were adequate for low-bandwidth, high-latency networks and computers with a single or low number of CPUs with client-server architectures and monolithic applications. Object Oriented Programming (OOP) and similar approaches can’t easily solve issues that arise with distributed system requirements.
The Actor model was invented by Carl Hewitt decades ago, it was visionary but was ahead of its time, but now hardware caught up with his vision. It was popularized by Erlang, one of the first mainstream programming languages to adopt the actor model as a core concept, contributing to its fame as being highly scalable and resilient to faults.
The Actor model addresses the following issues not addressed with more common models:
- One is encapsulation. Although OOP enforces encapsulation, because the code is executed inside each object avoiding inconsistent internal state, it’s a “leaky” abstraction, it only works when calls are executed in a single thread. With multi-threaded calls, encapsulation it’s only guaranteed with locking. But locking limits concurrency, it’s highly costly due to CPU context switching, it blocks execution and it’s error prone because of deadlocks.
- Other issue addressed is the illusion of shared memory. In current hardware architectures, data is mostly stored in memory caches, local to a cpu core. So now, data flows inside a computer like in a network, using message passing. Instead of hiding the nature of message passing, it must be incorporated in the model, keeping the internal state local, and explicitly propagate data and events as messages between concurrent entities.
- Lastly, there is the illusion of a call stack. The call stack is only single threaded, and when tasks are delegated between threads (or through the network), in case of a failure in the receiving thread the call stack error handling model doesn’t have a way to be notified of the error in the other thread.
In case of failure in concurrent systems, messages can be lost, and there must be a way to handle response timeout and recover from remote faults.
The Actor model handles these shortcomings using different paradigms. The first is adopt message passing as the way entities communicate, instead of calling methods on each other. After sending a message, the actor continues executing without blocking. Messages are queued in a message box and are handled sequentially, avoiding race conditions.
Actor’s state is local and not shared, changes are propagated using messages, which maps to how modern memory hierarchy works.
Because communication is not based on a call stack, errors are handled differently. Either there is an application error, or there is an internal fault inside the actor. In the former case, let’s say a validation error occurs, the error is described in the reply message whereas in the former case, because actors are organized in a hierarchy, the error is sent to the parent actor, and the parent actor is responsible to handle it. This process is called Actor supervision, and several strategies are available to handle actor failure.
As an implementation of the Actor model we will use Akka.NET (a port of the Akka framework in .NET) to develop practical examples. This framework provides transparent remote communication between components and a clustered architecture that can be scaled on demand.
Actors have an associated behaviour, that is, in each moment there is a function that defines the actions that are taken for each message that arrives. The behaviour can change in time, so the actions also change for a given message type. This simplifies implementing state machines.
After this session the attendees should have learned:
- Understand the Actor model and how the Akka framework implements it
- Why the Actor model simplifies the development of concurrent and distributed applications
- Model the application features as an hierarchy of actors, messages, commands, events and handlers
- Understand how to have a simpler error handling using actor supervision strategies
- How to use Actor behaviours to implement state machines and sagas