Best practices
Reactive principles
Kalix is ideally suited for the creation of Microservices. Microservices generally follow the Unix philosophy of "Do one thing and do it well." Kalix allows developers to build systems that follow the Reactive Principles without having to become distributed data or distributed computing experts. As a best practice, following the Reactive Principles in your design makes it easier to build distributed systems. Akkademy offers free courses on Reactive Architecture.
Domain Driven Design
Kalix makes it easy and fast to build services using the concepts of Domain Driven Design (DDD). While it’s not necessary to understand all the ins and outs of Domain Driven Design, you’ll find a few of the concepts that make building services even more straightforward below. Akkademy provides a free course on Domain Driven Design.
Bounded context
In Kalix, the combination of business logic and data is called an entity as it is used in domain-driven design, specifically an aggregate root. The data structure of your entity should be modeled to the needs of your API. As a best practice, and as you start to deploy more services, you should split the models into well-defined groups that are also known as bounded contexts. Each of these contexts will have autonomy to evolve the models it owns. Keeping each model within strict boundaries allows different modelling for entities that look similar but have slightly different meaning in each of the contexts.
Ubiquitous language
With Kalix, you can have multiple team members working on the same project. Everyone on the team (technical and non-technical) should be able to understand what the different events, actions, and other elements in Kalix do, which is why a common understanding (sometimes referred to as "ubiquitous language") is a great way to start.
Events first
Defining your data structures first, and splitting them into bounded contexts, will also help you think about all the different interactions your data needs to have. These interactions, like ItemAddedToShoppingCart
or LightbulbTurnedOn
are the events that are processed and persisted in Kalix. Defining your data structures first, makes it easier to think about which events are needed and where they fit into your design.
Message migration
Plan for the evolution of your messages. Commands and events use data structures that often must evolve over time. Fields get added, fields get removed, the meaning of fields may change. How do you handle this over time as your system grows and evolves?
The Protocol Buffer language offers a number of facilities that support evolution and migration across versions, but it is something that must be planned and handled carefully, so new versions of services don’t have problems reading older event journals that they can’t understand.
Deployment units
Each Kalix Service consists of one or more Components and is packaged and deployed to Kalix as a container image. This container image and its corresponding Kalix Service are the de facto deployment unit. Thus, when you couple multiple business concerns by packaging them in the same service, you limit Kalix’s ability to make the most efficient decisions to scale up or down.
Advantages of right-sized services
When you design a series of small services that don’t share code and can be deployed independently, you reap these benefits:
-
It is faster and less complex to write and debug them because they focus on a small set of operations, usually around a single business concern (be it with one or multiple types of Entities).
-
They simplify operational concerns and provide scalability because they can be deployed, stopped and started independently of the rest of the system.
-
They handle variations in load gracefully. If properly designed, multiple instances of the service can be started up when necessary to support more load: for example, if your system runs on Black Friday and the shopping cart service gets super busy, you can spin up more shopping carts to handle that load without also having to start up more copies of the catalog service. When the load decreases, these extra instances can be removed again, minimizing resource usage.
-
They handle failures gracefully. Components interact asynchronously with the rest of the world through messages and commands. If one instance, or even a whole service, fails, it is possible for the rest of the system to keep going with reduced capabilities. This prevents cascading failures that take down entire systems.
-
A team can focus on features of a single service at a time, without worrying about what other services or teams are doing, or when they are releasing, allowing more parallel teams to focus on other services, allowing your development efforts to scale as needed.
-
You can upgrade services in a "rolling" fashion, where new instances are started before older instances are removed, allowing new versions to be deployed with no downtime or interruption.