Introduction
Everyone in the software industry is following a trend where they are migrating to the Microservices Architecture, we have covered Microservices before in our blogs as well but in this one, we explain to you microservices architecture and Domain Driven Design in depth. We cover topics without hiding the complexity of the architecture while also making it simple for you to understand
We will start with the basic introduction of Domain-Driven Design and Microservices and go in depth later in the blog. Check out our other Reactive Architecture blog to know more about Microservices and Domain-Driven Design and Kalix.
What are Microservices and Domain-Driven Design?
Microservices Architecture is the practice of decoupling our application into loosely coupled services. Loosely coupled means that these services are not dependent on the state of other services, this helps us keep track of the Reactive Principles, Always remember that the goal of the microservices architecture is to make applications that are always Reactive. In short, we have to create applications that are Asynchronous, Message-Driven, Resilient, and Available.
This means that writing code for our application in the normal way will not help us, we follow a paradigm for making reactive applications called Reactive Programming, which involves writing code for errors, command handlers, and event handlers, reducing time for queries and commands on databases(how we can reduce the load on databases), using Event Sourcing with CQRS and using CRDTs.
As we can see from above although microservices do help in the performance of applications it adds a lot to the complexity of the application, writing long lines of code often is exhausting, and maintaining the various operations on microservices such as Security, Databases, Microservices are deployed on Distributed computing platforms or on virtual containers such as Docker and we require using Kubernetes for managing our docker containers, this adds a lot of labor for developers and when the application reaches an enterprise size it adds more complexity. We often ran into these problems until we tried Kalix by Lightbend which lets you develop your application following microservices architecture and taking care of all of the operations mentioned above allowing the developers to focus only on the development part of the application and improving the UX of the application.
Now, imagine you are migrating from a monolithic application to a microservices architecture how to do that? One of the techniques we can follow is called Domain-Driven Design. Domain Driven Design identifies different domains in the application, a domain is a sphere of knowledge that is relevant to the overall business logic that the software is trying to solve. It is the set of concepts, rules, and vocabulary that are used to understand and interact with that problem. These domains can also be subdivided into sub-domains if the application needs to.
Domains and if sub-domains if the application needs it works with three main things: Services, Entities, and Value Objects. When we will introduce Kalix we will mention one more thing in this category called the Action but for now let us work with the fact that “Domains and sub-domains work with Service, Entities and Value Objects”. In other words Services, entities, and value objects describe the answer to the question a domain is trying to solve.
Let us now define what are Services, Entities, and Value Objects.
Services, Entities, and Value Objects
As briefly we have described Sevices, entities, and value objects as the “answer to the question the domain is trying to solve. A Service contains the business logic of the application. In Microservices Architecture, Services are typically stateless and reusable, and they are often used to implement cross-cutting concerns, such as security, logging, and caching.
There are two main types of services in DDD:
Domain services: Domain services encapsulate business logic that is related to the domain model. They are typically used to implement domain-specific operations, such as calculating the total price of an order or validating a user's input.
Application services: Application services encapsulate business logic that is related to the application's user interface. They are typically used to interact with the domain model and to provide data to the user interface.
Services are an important concept in DDD, because they help to decouple the domain model from the user interface and the infrastructure. This makes the software easier to understand and maintain, and it also helps to improve the testability of the software.
Here are some of the benefits of using services in DDD:
Decoupling: Services decouple the domain model from the user interface and the infrastructure. This makes the software easier to understand and maintain, and it also helps to improve the testability of the software.
Reusability: Services can be reused in different parts of the application. This makes the software more maintainable and scalable.
Cohesion: Services are typically focused on a single unit of business logic. This makes the code more cohesive and easier to understand.
Testability: Services are typically stateless and reusable. This makes them easier to test, which helps to improve the quality of the software.
Here are some of the drawbacks of using services in DDD:
Complexity: Services can add complexity to the software. This is especially true if the services are not well-designed.
Communication: Services need to communicate with each other. This can introduce latency and complexity.
Dependencies: Services can have dependencies on each other. This can make the software more difficult to understand and maintain.
These services contain Entities which are data objects which define real-world objects, they have a unique identity and a lifespan which means that they can be created, updated , and deleted. For example: If we are buiding an application for a mall then the Customer Entity will contain the data for a person who has purchased a product from them.
Entities have a state, which means that they can change over time. This state is typically represented by the entity's properties. For example, a customer entity might have properties such as name, address, and email address. These properties can change over time, such as when the customer changes their address or email address.
On the other hand, value objects are referred to as a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object. Examples of value objects are objects representing an amount of money or a date range. Value objects are also immutable. Value objects are often used to represent concepts that are important in the domain, but that do not have a unique identity in the real world. For example:
Address: An address value object represents a physical address. It has three properties: the street address, the city, and the state.
Phone number: A phone number value object represents a phone number. It has three properties: the country code, the area code, and the phone number.
Email address: An email address value object represents an email address. It has two properties: the email address and the domain name.
Product identifier: A product identifier value object represents a unique identifier for a product. It has one property: the product identifier.
Quantity: A quantity value object represents a quantity of something. It has two properties: the amount and the unit of measurement
So, to summarise Domain-Driven Design is a complementary approach to software design that focuses on the business logic of the application, Microservices are a collection of loosely coupled services that encapsulate this business logic. Domain in domain-driven design refers to the problem the domain is solving for, these domains contain Services, Entities, and Value Objects which represent the implementation of the problem. Now, as you can see these microservices follow a complex approach, and hopefully I have managed to make them a little more subtle, However when we try writing code to implement this approach this becomes much more complicated, not to mention the technical overhead with maintaining these microservices, hence a developer platform where some of the overhead for the developer teams gets lessened, Kalix by Lightbend does exactly that, it takes care of the operation part of these microservices such as Kubernetes, databases, security and allows the developers to focus on the business capabilities of the application.
Kalix By Lightbend
Kalix here makes the complicated approach much simpler by taking care of all the operations that a developer might have to deal with whilst distracting him away from working on the business logic, it is fairly obvious that this gives developer the room for creating a much better user experience for the users. Kalix also introduces a fourth component when working with domains as we have mentioned above: Actions, these actions define the use-cases of a service and they are often used in conjunction with Services. They have also categorized entities as Value-Entities and Event-Sourced Entities, and also Views, views help us in querying the database and getting multiple values at the same time. We can say that value entities work as the value objects here where they hold the state of the domain whilst being immutable. Event-sourced and Replicated Entities refer to the entities which capsulate events as a way of holding state whereas Replicated entities tries to main availability whilst maintaining the integrity of the data and providing eventual consistency.
As of now, Kalix provides its SDK in Java, Scala, and JavaScript.
Kalix also provides Message Brokers and Event Buses integration which also make up an important part of the whole Microservices architecture by promoting message-driven asynchronous communication.
Asynchronous Communication
Asynchronous communication is essential for the microservices architecture as they follow reactive architecture in which resilience and reactivity are two of the foundations of the reactive principles which means that even if one microservice is operating and in function, this shouldn’t affect the other microservices, even if it fails it shouldn't disturb the other microservices, they should keep functioning just fine.
This can be done by various techniques such as using Message brokers, event buses, CRDTs, and also circuit breakers for handling errors.
Message Brokers
Message Brokers help in asynchronous communication by being a central repository for all the microservices and implementing Pub/Sub Architecture. This means that if we have a microservice that communicates with 3 other microservices which depend on this message to implement some kind of functionality, this process should be seamless. If we are in this kind of situation and we are using a message broker these microservices become highly dependent on each other or in other words, tightly coupled we don't want that require loosely coupled with asynchronous communication, with the approach without message brokers if one microservice fails then other microservices will also fail and we don’t want to do that. Some of the message brokers that can be used are : Kafka, RabbitMQ, etc. Remember to always use a reliable Message bus as they help in low latency and storage.
Pub/Sub Architecture
Pub/Sub stands for Publish/Subscribe. It is an architectural pattern for decoupling senders and receivers of messages. In a pub/sub architecture, senders do not send messages directly to receivers. Instead, they publish messages to a topic. Receivers subscribe to topics, and they receive messages that are published to the topics that they subscribe to.
Pub/sub-architectures are often used in distributed systems, where it is important to decouple senders and receivers. This is because it allows senders and receivers to scale independently. Senders do not need to know about the receivers, and receivers do not need to know about the senders. This makes it easy to add or remove senders and receivers, and it also makes it easy to scale the system.
Pub/sub-architectures are also often used in event-driven systems. In an event-driven system, events are published to topics, and receivers subscribe to topics to receive events. This allows events to be published and received asynchronously, which can improve the performance of the system.
There are many different pub/sub implementations available. Some popular pub/sub implementations include:
Google Cloud Pub/Sub
Amazon Simple Notification Service (SNS)
Apache Kafka
RabbitMQ
These implementations offer a variety of features, such as scalability, reliability, and security. The best implementation for a particular application will depend on the specific requirements of the application.
Here are some of the benefits of using a pub/sub architecture:
Decoupling: Pub/sub architectures decouple senders and receivers, which makes it easier to scale the system and to add or remove senders and receivers.
Asynchronous communication: Pub/sub architectures allow for asynchronous communication, which can improve the performance of the system.
Scalability: Pub/sub-architectures are scalable, which means that they can handle a large number of messages.
Reliability: Pub/sub-architectures are reliable, which means that messages are not lost.
Security: Pub/sub-architectures can be secured, which means that messages are protected from unauthorized access.
Here are some of the challenges of using a pub/sub architecture:
Complexity: Pub/sub-architectures can be complex to implement and to manage.
Cost: Pub/sub-architectures can be expensive, especially if a large number of messages are being sent or received.
Latency: Pub/sub-architectures can have latency, which means that there may be a delay between when a message is published and when it is received.
Event Buses
Event Buses in functionality are similar to message brokers just instead of serving as a central repository for messages they serve as the repository for events and follow the same publish/subscribe architecture. The same message brokers can also be used as event stores. For example: Apache Kafka, RabbitMQ
CRDTs
CRDTs (Conflict-free replicated data types) are a family of data types that are designed to be replicated across multiple nodes in a distributed system. CRDTs are conflict-free, which means that they can be updated concurrently by different nodes without the need for coordination. This makes them well-suited for applications that require real-time collaboration, such as chat applications and collaborative editing applications. CRDTs are a powerful tool for building distributed applications that require real-time collaboration. They are conflict-free, which means that they can be updated concurrently by different nodes without the need for coordination. This makes them well-suited for applications that require high availability and low latency.
Here are some of the benefits of using CRDTs:
Conflict-free: CRDTs are conflict-free, which means that they can be updated concurrently by different nodes without the need for coordination. This makes them well-suited for applications that require high availability and low latency.
Scalable: CRDTs are scalable, which means that they can be used to build large-scale distributed applications.
Efficient: CRDTs are efficient, which means that they can be used to build applications that are responsive and performant.
Circuit breakers
In the context of microservices architecture, circuit breakers are a design pattern used to improve the resilience and fault tolerance of the system. The pattern is inspired by electrical circuit breakers, which are devices that prevent electrical circuits from overloading and causing damage.
In a microservices environment, where multiple services interact with each other, failures can occur due to various reasons like service unavailability, slow responses, or network issues. When one service fails or slows down, it can lead to a cascading failure that affects other services and degrades the overall system performance.
A circuit breaker acts as a safety mechanism between services to detect and handle failures proactively. Here's how it works:
1. Monitoring: The circuit breaker constantly monitors the interactions between services. It tracks the number of requests made to a particular service and observes the response times.
2. Thresholds: The circuit breaker sets predefined thresholds for the number of failures and response times. If the number of failures or response times exceeds these thresholds, the circuit breaker trips.
3. Tripped State: When the circuit breaker is tripped, it stops forwarding requests to the failing service. Instead, it immediately returns an error response to the client without even attempting to make the request to the service.
4. Open State: The circuit breaker remains in an "open" state for a specified period. During this time, any further requests to the failing service are automatically rejected, and the system doesn't waste resources trying to interact with an unreliable service.
5. Half-Open State: After the specified time period, the circuit breaker enters a "half-open" state. During this phase, it allows a limited number of requests to the failing service to check if it has recovered.
6. Close/Open Transition: Based on the response to the limited requests, the circuit breaker decides whether to return to the "closed" state (if the service is functioning correctly again) or return to the "open" state (if the service is still not responsive).
By implementing circuit breakers, microservices can fail gracefully and prevent cascading failures and also, therefore, helping in asynchronous communication
We would advise you to take a better look at Kalix by going through its documentation where you will find all the steps from downloading the SDK to writing high-level code for your application
We hope that the blog above helped you understand the complex microservices architecture in depth and have at the same time easier for everyone to understand.
For any queries feel free to write to us at hello@fusionpact.com
Comments