-
Notifications
You must be signed in to change notification settings - Fork 13
Encapsulated Domain Logic
| Main > Key Concepts > Encapsulated Domain Logic |
|---|
The exact internal form of a microservice is still under discussing by the community.
Even though most of microservices are written on top of mature frameworks like .NET Core, NodeJS and Spring Boot, these were not exactly built with microservice concept in mind. They give only indirect or adapted recommendation from other design patterns.
One of these loose adaptations is the use of MVC Controllers in microservices as if they were intermediating front-end Views and back-end Models like front-end Controllers do. This comes from the use of server-rendered web pages where (server-side, application server) Controllers respond to (client-side, browser) Views, dealing with an (also server-side) object model.
But inside microservices, their purpose is quite different. They are the very "Model" logic: the one handling application business logic, validation logic, and database access logic..
The real controllers of today are likely Javascript ones, running on browsers and calling remote back-ends. Actually, the back-end is the M of MVC - when there is a front-end (VC) calling it synchronously via REST.
So, yes, a microservice can be thought of as the M of MVC, but it can also be other things. It can be called by other means and participate in other patterns.

So Liquid takes a step forward to prescribe the internals of a microservice thus helping maximize domain logic encapsulation and reuse, even if that microservice does not respond to REST calls, i.e. even if it does not have Controller classes whatsoever.
-55f49eda-d92f-4631-892e-e39a84db2262.png =900x)
The first thing to have in mind is that a Controller should only behave as a facade to microservice's domain classes.
In fact, no microservice controller can call other controllers either be called by other classes or protocols besides REST (HTTP). So putting any domain (business) logic inside them prevents reuse and reasonable modularization of code.
Accordingly, controllers should be responsible for only validating inputs received via http, calling "domain services" and returning resulting data and status in http conventions.
public class BasketController : LightController
{
[HttpPost("/")]
public IActionResult Add([FromBody] BasketViewModel basket)
{
ValidateInput(basket);
var response = Factory<BasketService>().Add(basket);
return Result(response);
}
For CRUD microservices, Liquid proposes domain classes named "services" that are derived from Liquid LightService*.
In this prescribed programming model, microservices are small sets of "service classes" that are related to some bounded (domain) context, then packed, deployed and executed as units to be accessed via facades - one for each supported method-and-protocol.
The same logic could then be called from a complete different API such as an asynchronous messaging one:
[MessageBus("ESHOP")]
public class BasketWorker : LightWorker
{
[Queue("new_basket",1)]
public void Add(BasketMessage basket)
{
ValidateInput(basket);
var response = Factory<BasketService>().Add(basket.MapTo<BasketViewModel>());
Terminate(response);
}
}
(*) IMPORTANT: Liquid's LightService concept should not be confused with the conventional use of server proxy classes also named as "Services".
The concept of services proposed here allows to delegate behavior to other services thus improving the internal modularization of a microservice.
Take the following example where a service called Basket delegates the discount logic to another service called Discount.
public DomainResponse Add(BasketViewModel basketVM)
{
BasketModel basket = new BasketModel();
basket.MapFrom(basketVM);
basket.discountRate = FactoryDelegate<DiscountService>().Calculate(basket.items);
var res = await Repository.AddOrUpdateAsync(basket);
return Response(res);
}
Note that dependency injections for the new instance of DiscountService are done automatically by Liquid.
The use of controllers as simple facades is more common in CQRS microservices.
Liquid also has classes for commands and queries (LightCommand and LightQuery and respectively handlers) that can be used by controllers (and others) the same way:
public class BasketController : LightController
{
[HttpGet("/")]
public IActionResult Filter([FromBody] FilterBasketsQuery query)
{
ValidateInput(query);
var response = await Factory<FilterBasketsHandler>().Execute(query);
return Result(response);
}
[HttpPost("/")]
public IActionResult Add([FromBody] AddBasketCommand command)
{
ValidateInput(command);
var response = await Factory<AddBasketHandler>().Execute(command);
return Result(response);
}
However, unlike services, commands and queries are meant to encompass all business logic related to them. No further decomposition is usually done. No (internal) delegation.
The OO decomposition design that supports service orientation is thus compromised in terms of internal classes of such (micro)services, but the general API construction can still be object oriented if domain abstractions are preserved at facade level.
Nevertheless delegation could always be done through events sent asynchronously: both externally (for other microservices) and internally (for the microservice itself).
Lastly, Liquid does simply prescribe this type of (business) logic encapsulation into domain classes. But that is not mandatory.
If a developer would rather put logic in controllers or even in data (POCO/POJO) classes he or she might still get general benefits from using Liquid.