Working with domain services
samson baraka
Lead Software Engineer | Backend Engineer | ERP & CRM Systems Engineer | Agile Practitioner | Microservices | AI Driven Systems | Java & Spring Developer | Enterprise Application Developer | Web developer
When modeling a problem domain, we'll certainly face situations where the task at hand does not fit adequately into any of the object categories that we've seen so far in the domain hexagon: entities, value objects, and aggregates. Earlier, we met a situation where we removed from the Router entity a method responsible for retrieving a list of routers. That method seemed to be in the wrong place because, in our topology and network inventory scenario, a router usually doesn't list other routers. To deal with this cumbersome situation, we've refactored the router list method in a separate object. Eric Evans calls such objects domain services.
I believe it's important to distinguish domain services from any other type of service. For example, in Model-View-Controller (MVC) architectures, services are often seen as bridges that connect the different facets of an application, handling data and orchestrating calls within and outside the system. Their usage is often associated with software development frameworks such as Spring that even have a service annotation. But independent of the context, I believe the main difference between distinguished service types lies not in the meaning but in the scope.
What makes something a service? It's the ability to perform some worthwhile effort. This characteristic is inherent to any service, both in the real world and with computers. However, in the latter case, we should care about Separation of Concerns (SoC), modularization, decoupling, and other relevant matter for good architecture. It's based on those concerns that we nail down domain services to the realm of the Domain hexagon. They perform worthwhile tasks—as any other services—but within the constrained scope of our problem domain. This means domain services should not call services or other objects that operate in application or framework hexagons. Instead, objects from those hexagons are clients who call domain services.
In the previous section, we created the following two methods in our Router entity class, which is also the aggregate root:
领英推荐
public void addNetworkToSwitch(Network network){
this.networkSwitch = networkSwitch.addNetwork(network);
}
public Network createNetwork(IP address, String name, long cidr){
return new Network(address, name, cidr);
}
In the following code snippet, we have a service class operating over those two Router entity methods:
public class NetworkOperation {
final private int MINIMUM_ALLOWED_CIDR = 8;
public void createNewNetwork(Router router, IP address, String name, int cidr) {
if(cidr < MINIMUM_ALLOWED_CIDR)
throw new IllegalArgumentException("CIDR is below"+MINIMUM_ALLOWED_CIDR);
if(isNetworkAvailable(router, address))
throw new IllegalArgumentException("Address already exist");
Network network = router.createNetwork(address,name,cidr);
router.addNetworkToSwitch(network);
}
private boolean isNetworkAvailable(Router router, IP address){
var availability = true;
for (Network network : router.retrieveNetworks()) {
if(network.getAddress().equals(address) && network.getCidr() == cidr)
availability = false;
break;
}
return availability;
}
}
We have a method called createNewNetwork that is responsible for creating a new network object and adding it to the switch linked to our router. We should meet two constraints to be able to create a network. The first, simple one checks if the minimum Classless Inter-Domain Routing (CIDR) has not been violated. The second constraint is somewhat more elaborate. It verifies whether the network address is already being used anywhere on the whole network.
With this approach, we're delegating to the NetworkOperation domain service class the responsibility to deal with tasks that don't fit quite well into entities or value objects. That's also a valuable effort to prevent entity and value object classes from growing too large with far more features than necessary according to the problem domain.
Until now, we've been dealing with invariants directly on entities, value objects, or service classes. Next, we'll see an approach to accommodate those invariants in a more orderly and organized way.