17
Domain Services

WHAT’S IN THIS CHAPTER?

  • An introduction to domain services
  • A disambiguation from other types of service
  • Advice about when to consider using domain services
  • Examples of domain services for a variety of domains
  • A discussion, with several examples and techniques, on how to use domain services from within the application layer and the domain model

Wrox.com Code Downloads for This Chapter

The wrox.com code downloads for this chapter are found at www.wrox.com/go/domaindrivendesign on the Download Code tab. The code is in the Chapter 17 download and individually named according to the names throughout the chapter.

When building domain models, you sometimes come across concepts or behavior that do not logically sit comfortably within an entity or aggregate in the system. This is an implicit sign that you may need to create a domain service.

Confusingly, service is an overloaded term. Fortunately, though, domain services are easily distinguished based on two defining characteristics: they represent domain concepts, and they are stateless. You are most likely to use domain services to orchestrate entities and encapsulate business policies rather than carry out infrastructural plumbing; leave that to application services.

In this chapter, you learn how to build domain services that cater to different scenarios. These scenarios include exposing domain services as contracts that are implemented outside the domain model, and building pure domain services that contain important business rules and reside entirely within the domain model. This chapter also shows some of the ways that your domain services can be consumed from both the service layer and the domain model.

Before moving on to the concrete examples, this chapter elaborates on the motives for using domain services and provides further clarification of precisely what they are.

Understanding Domain Services

This book contains information about Service Oriented Architecture (SOA), application services, and domain services. None of these concepts are closely related, yet there is still lots of confusion among developers and on mailing lists about the differences between them. Conceptually, domain services represent domain concepts; they are behaviors that exist in the problem domain, they occur in conversations with domain experts, and they are certainly part of the ubiquitous language (UL). If you can remember those important characteristics, you will find domain services to be a useful tool.

A few technical characteristics of domain services are important to be aware of so that you can model them in your code effectively. You will learn about them shortly.

When to Use a Domain Service

You’ve spoken to domain experts about a domain concept that involves multiple entities, but you’re unsure about which entity “owns” the behavior. It doesn’t appear to belong to any of them, and it seems awkward when you try to force-fit it onto either of them. This pattern of thinking is a strong indicator of the need for a domain service.

Encapsulating Business Policies and Processes

A primary concern for domain services is carrying out some behavior involving entities or value objects. This can be demonstrated using two Competitor entities and an OnlineDeathmatch entity, all taken from the domain model of an online gaming system. They are shown in Listing 17-1 and Listing 17-2, respectively.


On completion of an OnlineDeathMatch, each player has his score updated based on how he performed in the game. One option for locating this logic is to place the score calculation and reward logic within the OnlineDeathmatch, as shown in Listing 17-3.

One big problem with CalculateNewPlayerScores() being part of the OnlineDeathMatch is that the rules are flexible; the business may want to award double points or hand out special prizes at certain times. What the business is saying in cases like this is that score and reward calculation are fundamentally important concepts within themselves. The same special promotions are often applied to different types of games: TeamBattle, CaptureTheFlag, GangCrusade, and so on.

This scenario is hinting at the need for domain services that encapsulate the single responsibility of a specific score or reward policy. Listing 17-4 shows the IGamingScorePolicy and IGamingRewardPolicy domain service interfaces that you can inject and apply into an OnlineDeathmatch or any other type of domain object that needs them.


Using the approach in Listing 17-4, you can switch in and out specific score and loyalty calculations according to the latest business promotions. Equally as importantly, you can now make these concepts explicit in the domain. For example, when a business is running its “free 12-month subscription for high scores” promotion, you can model this explicitly in the domain as a Free12MonthSubscriptionForHighScoresRewardPolicy domain service, as shown in Listing 17-5.

Listing 17-5 shows the Free12MonthSubscriptionForHighScoresRewardPolicy domain service that explicitly defines an important domain concept that exists within the UL. This domain service contains important business rules, lives inside the domain model, and hence is pure. However, some domain services are not implemented within the domain model; instead, their implementation lives in the service layer.

Representing Contracts

The other broad use case for domain services is as a contract—where the concept itself is important to the domain, but the implementation relies on infrastructure that cannot be used in the domain model. An example of this is ShippingRouteFinder, shown in Listing 17-6. You can see that it makes a web request to the routing API so that it can find the available routes for the given journey endpoints.

Importantly, the logic of making HTTP requests to a web server cannot live in the domain because it is an infrastructural concern; therefore, the implementation of the ShippingRouteFinder domain service in Listing 17-6 cannot live in the domain model. But the concept is part of the UL, so the interface must.

You can use domain services as contracts for a variety of scenarios, including:

  • Entity identification
  • Exchange rate lookup
  • Tax lookup
  • Real-time notifications

Anatomy of a Domain Service

Domain services have three fundamental technical characteristics: they represent behavior, and thus have no identity; they are stateless; and they often orchestrate multiple entities or domain objects. In addition to the examples in the previous section, the RomanceOMeter in Listing 17-7 highlights these characteristics. The RomanceOMeter is inspired by a similar concept in the domain model of an online dating website where it is used to assess how compatible two love seekers are.

There’s no ID or identification-related state on the RomanceOMeter in Listing 17-7. It is pure behavior, containing only AssessCompatibility() that performs the romance calculation. As you can also see, it keeps no state and orchestrates multiple LoveSeeker entities; the calculation is based entirely on the inputs. In doing so, it satisfies the stateless requirement and exemplifies how domain services orchestrate other domain objects.

Avoiding Anemic Domain Models

After accepting that not all domain logic needs to live on entities directly and that domain services are a useful concept, you need to be careful not to push too much logic into domain services, which can lead to inaccurate, confusing, and anemic domain models with low conceptual cohesion. Clearly, that would counter some of DDD’s major benefits. However, as long as you pay enough attention, it’s unlikely to become a problem.

Finding the right balance between too few and too many domain services is easier with experience. But care and logical thinking help you get the majority of decisions correct. For instance, one pattern of thinking that should evoke thoughts of using a domain service occurs when adding new behavior to an entity is an awkward fit and just doesn’t feel right, and it doesn’t align with what domain experts are saying. You can then discuss the issue further with those domain experts and listen to how they describe the concept. Do they always refer to an entity when discussing the concept, or do they discuss it in isolation? At the other extreme, if you are creating a lot of domain services, maybe your thought process is a little too liberal.

Contrasting with Application Services

A common source of confusion is differentiating application from domain services. Once you understand the conceptual role of both, though, you’ll never be confused again. As you’ve seen in this chapter, domain services represent concepts that exist within the problem domain, and at a minimum, their interface lives in the domain model. Conversely, application services do not represent domain concepts, and they don’t contain business rules. Also, they don’t even partially live in the domain model—not even their interfaces. As you will see in Chapter 25, “Commands: Application Service Patterns for Processing Business Use Cases,” application services live in the service layer and deal will pulling together infrastructural concerns, like transactions, to carry out full business use cases.

A question that appears regularly on DDD forums is: What’s the ideal way to handle authentication and authorization in a domain service? The simple answer is that you don’t, because that is a responsibility of the service layer. This example typifies the confusion around the two types of service and helps you understand the role of each by knowing why it is a bad question (but completely understandable due to the overloaded term service).

You may still be thinking that domain and application services are similar in that they both may have to deal with infrastructural concerns. It’s also easy to make a distinction here. Domain services rely on infrastructure that is used to inform domain logic. Conversely, infrastructural concerns in an application service are there to enable the domain model to execute correctly. A domain service makes HTTP calls to a web service or writes something to disk as part of domain logic, but an application service wraps the domain model in a transaction or creates database connections so that the code can run as a single use case.

Utilizing Domain Services

Having learned about the concept of domain services and seen practical examples, the remaining detail is in understanding how to use them. Some options require little explanation, as is the case with using domain services inside application services. Contentiously, though, domain services often need to be used as a step in domain processes that reside fully in the domain model. This leads to the debate of injecting domain services into entities. As you’ll see shortly, this is a solved problem, but no solution is without its trade-offs and detractors.

In the Service Layer

Starting with the easiest case, domain services can be put to use within application services. As part of its role in fulfilling a full business use case, an application service can pull the relevant entities out of a repository and pass them into a domain service, as shown in Listing 17-8.

In Listing 17-8, the MultiMemberInsurancePremium application service pieces together the IMultiMemberPremiumCalculator with the Policy and Member entities that are required by its CalculatePremium(). This illustrates that, when used in the service layer, domain services and other domain objects like entities can be easily pieced together as needed. This convenience may not always be achievable, though, like in cases where an entity appears to be dependent on a domain service and the domain objects need to be coordinated within the domain model.

In the Domain

Sometimes an entity needs a domain service to carry out its behavior in a way that precludes piecing them together in an application service. A typical example is when a notification needs to occur following an entity executing some task. Listing 17-9 highlights this type of scenario by showing a RestaurantBooking entity that triggers a NotifyBookingConfirmation when a customer confirms a restaurant booking. The RestaurantBooking entity directly depends on the IRestaurantNotifier domain service to notify the restaurant of the booking confirmation.

In Listing 17-9, the challenge is to have the restaurantNotifier, an instance of IRestaurantNotifier, available within the scope of ConfirmBooking(). It may seem easy with plain old constructor injection as the obvious choice. It’s not always that straightforward, though; often object-relational mappers (ORM)s are used to manage the life cycle of entities, removing the developer’s ability to pass in dependencies at object construction. Each of the following techniques aims to alleviate this problem.

Manually Wiring Up

If an entity or other domain object depends on a domain service, you can pass the relevant service into the constructor if you are managing object construction yourself. Listing 17-10 exemplifies the desirable solution of using a factory method to solve this problem.

Listing 17-10 shows how you can pass a RestaurantNotifier domain service into the constructor of the RestaurantBooking entity that is manually constructed inside the CreateBooking() factory method. Factories are a common pattern that you see in many codebases. So far so good. But this solution becomes problematic in DDD when you don’t handle construction of the entity yourself—typically when an ORM loads an entity from persistence.

You can still work around an object’s life cycle that an ORM is managing, but the solution is not necessarily pretty. Listing 17-11 shows how your factory methods can add a second stage of object construction that sets the domain service as a property on the entity after an ORM has constructed it. In a similar fashion, you can have an Init() method that takes all the dependencies and sets them, like a pseudo-constructor. Either way, you’re open to the problem of forgetting that construction of the object occurs in two phases, which can lead to inconsistent states in production systems with lots of ensuing head scratching.

You may have thought of other problems associated with two-phase construction, such as remembering to apply the pattern consistently across the codebase and being disciplined to ensure that Init() is not called again after the object has been constructed.

If all this manual configuration is undesirable for you, you may prefer to offload some of it to an Inversion of Control (IoC) container by using dependency injection.

Using Dependency Injection

Another approach is to dependency-inject domain services into the entities that need to use them. Once a domain service has been added as a constructor parameter, the remaining task is to wire up the desired implementation using standard dependency injection. Using dependency injection saves you the hassle of manually constructing objects. It’s mainly a question of style whether you choose this or manually wiring up your dependencies, though, because the problem of dealing with ORMs may still be present.

With some ORMs, it’s possible to have them wire up dependencies that your IoC container manages. Unfortunately, though, with many it is not. However, if you’re still determined to use an IoC container and an ORM, the service locator pattern is one potential solution.

Using a Service Locator

Advance warning: the service locator pattern is fairly controversial; you’ll learn why shortly. However, you can see in Listing 17-12 how it solves the problem of allowing IoC containers to handle setting the dependencies of entities that are loaded by ORMs, without the need for manual intervention.

By taking the hard-coded dependency on the ServiceLocator class shown in Listing 17-12, you can load all of an entity’s dependencies from an IoC container during object construction after an ORM has initialized it. This removes the need for manual construction steps. But there’s a price; now the entity is tightly coupled to the ServiceLocator—an infrastructural concern that ideally you don’t want polluting the domain model.

The real problems with using a service locator arise from the tight coupling; you have to mock mocks in your tests, for example. Another problem is the obscuring of an object’s dependencies, because they’re no longer passed into the constructor.

Applying Double Dispatch

If you’re happy to forego passing domain services into entities at construction time, you have the option of instead passing them as method arguments using the double dispatch pattern. With double dispatch, a domain service is passed into a method on an entity, and the entity then passes itself into a method on the domain service, as shown in Listing 17-13.

As Listing 17-13 shows, domain services no longer need to be supplied into an entity’s constructor. Instead, they can be passed into methods on an entity by an application service. This approach hasn’t proven to be massively popular for a few reasons. Some feel that passing dependencies into methods requires callers of the method to supply the dependency—a responsibility that shouldn’t belong to them. Others argue that dependencies normally go via the constructor because they should not be part of a method’s signature; they’re an implementation detail that may be changed, whereas the method signature should not be so volatile.

So double dispatch is not to everyone’s liking, but neither are any of the other options presented so far. It’s definitely best to explore the idea yourself and assess how it may work on your projects. One approach that has become popular, though, is using the domain events pattern, with the promise of truly decoupling entities from domain services.

Decoupling with Domain Events

An interesting pattern that completely avoids the need for injecting domain services into entities is domain events. When important actions occur, an entity can raise a domain event that is handled by subscribers who are registered for that event. As you might have guessed, domain services can reside within a subscriber, and therefore, not an entity.

Listing 17-14 highlights the RestaurantBooking entity raising a BookingConfirmedByCustomer domain event. Listing 17-15 shows an event handler, also known as a subscriber, which handles this type of event by invoking the IRestaurantNotifier domain service. Finally, Listing 17-16 shows the small amount of work required to stitch these components together.



Listings 17-14 to 17-16 show a minimum possible example of the domain events pattern applied to the ongoing restaurant booking example. Chapter 18, “Domain Events,” is dedicated to the domain events pattern and provides more in-depth examples that explore the strengths and weaknesses of the pattern. One obvious drawback that you may have discerned from Listings 17-14 to 17-16 is that the logic is now distributed between the RestaurantBooking entity and the NotifyRestaurantOnCustomerBookingConfirmation event handler, whereas with other patterns this would be a single piece of sequential code.

Should Entities Even Know About Domain Services?

In the previous example, you saw how domain events preclude injecting domain services into entities, whereas each of the other patterns goes in the opposite direction and tries to find a way to make the dependency feasible. The latter is a fairly contentious approach within the DDD community; many practitioners argue that it’s just a bad idea. Ultimately, you need to use context, personal preferences, and your experience to decide which option you like the best.

The Salient Points

  • Sometimes behavior does not belong to an entity or value object yet is still an important domain concept; this is hinting at the need for a domain service.
  • Domain services represent domain concepts; they are part of the UL.
  • Domain services are often used to orchestrate entities and value objects as part of stateless operations.
  • Too many domain services can lead to an anemic domain model that does not align well with the problem domain.
  • Too few domain services can lead to logic being incorrectly located on entities or value objects. This causes distinct concepts to be mixed up, which reduces clarity.
  • Domain services are also used as contracts, where the interface lives in the domain model, but the implementation does not.
  • When an entity depends on a domain service, a variety of options can be used, including dependency injection, double dispatch, and domain events.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.125.205