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 new tab 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. Lightbend Academy offers free courses on Reactive Architecture.

Domain Driven Design

Domain-driven design (DDD) is the concept that the structure and language of software code (class names, class methods, class variables) should match the business domain. For example, if a software processes loan applications, it might have classes such as LoanApplication and Customer, and methods such as AcceptOffer and Withdraw. — Wikipedia new tab

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 straight-forward below. Lightbend provides a free course on Domain Driven Designnew tab.

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.

bounded context

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

While a container image can hold one or more services, it is typical to package one service per image. Kalix scales the services in a container image together. When you couple multiple services by packaging them in the same image, you limit the ability of Kalix to make the most efficient decisions to scale up or down.

The 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 data Entity.

  • They simplify operational concerns and provide scalability because they can be deployed, stopped and started independently of the rest of the system, further simplifying operational concerns.

  • 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.