25
Commands: Application Service Patterns for Processing Business Use Cases

WHAT’S IN THIS CHAPTER?

  • A discussion of the differences between application logic and domain logic
  • Examples of concerns that are handled in the service layer
  • Design patterns that can be applied to application services
  • Suggestions for testing application services

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 25 download and individually named according to the names throughout the chapter.

Many of Domain-Driven Design (DDD’s) benefits arise from disciplined use of a project’s ubiquitous language (UL)—both in conversation and in code. One of the big challenges you face, though, is maintaining explicitness of domain concepts in code as you try to keep them isolated from purely technical concerns. For instance, when you are knowledge-crunching with domain experts, talking them through your domain model, it’s not ideal to clutter your thinking—or the conversation—with threads, sockets, or database connections. Therefore, to maximize the explicitness of your domain model, a clear separation between real-world domain concepts and purely technical concerns is highly desirable. This separation is one of the important roles carried out by application services, which belong to the application service layer.

Logically, the application service layer sits above the domain and is dependent upon it. This means that a crucial responsibility of application services is to coordinate with the domain to carry out full business use cases. As part of this responsibility, an application service has to translate inputs and outputs to protect domain structure, and it often needs to communicate with other bounded contexts using REST, messaging, and other concepts discussed in Part II, “Strategic Patterns: Communicating Between Bounded Contexts.” Figure 25.1 provides a high-level illustration of the role of application services.

images

FIGURE 25.1 Where application services fit in.

Figure 25.1 sets the context for this chapter by visualizing how application services mediate between the domain and external services such as other bounded contexts. Before moving on to technical examples that demonstrate the responsibilities of application services, it is important to be clear about the distinction between application and domain logic. Even for experienced DDD practitioners, there are occasionally some challenges with this distinction.

Differentiating Application Logic and Domain Logic

Understanding the difference between application logic and domain logic is crucial if you want to produce a model that accentuates domain concepts and isolates them from purely technical details. For the most part, it is not a difficult task, although there is a small gray area. This section aims to provide a clear picture so you rarely have to worry about where your code should live.

Application Logic

As a starting point, you can think of application services as having two general responsibilities. First, they are responsible for infrastructural concerns: managing transactions, sending e-mails, and similar technical tasks. In addition, application services have to coordinate with the domain to carry out full business use cases. Carrying out these responsibilities correctly helps prevent domain logic from being obfuscated or incorrectly located in application services.

To demonstrate the role of application services, this section walks through the creation of an application service that can be used in an online gambling application. It manages the Recommend-a-Friend use case, in which loyal gamblers are rewarded with $50 of free credit if they can entice their friends to sign up. Their friends are also rewarded with a $50 credit when their account is created. As an extra bonus, the business has decided the referrer’s loyalty status should be upgraded to gold. Figure 25.2 illustrates the Recommend-a-Friend use case.

images

FIGURE 25.2 The Recommend-a-Friend use case.

Infrastructural Concerns

To invoke the functionality of your domain model, you need to carry out a number of infrastructural tasks. Setting up database connections is one common example. When you do a good job of isolating this infrastructural code, you’re rewarded with a more maintainable domain model, free from technical clutter. In the following short sections, you see examples of infrastructural concerns being handled in the RecommendAFriendService—an application service that is responsible for carrying out the Recommend-a-Friend use case in the online gambling scenario. One of the first tasks this application service has to deal with, like many others, is validating inputs.

Application-Level Validation

Examples of application-level validation include checking that parameters are the correct data type, the correct format, and the correct length. They aren’t business rules that domain experts care about, but they can still cause error conditions in a system. Rather than cluttering domain logic with these technical details, you can perform this type of validation in application services.

The RecommendAFriendService takes the account ID of the referrer and basic account details of the friend who is signing up. An initial implementation of the RecommendAFriendService carrying out application-level validation is shown in Listing 25-1.

LISTING 25-1 exemplifies the role of application-level validation. Each clause in Validate() checks a technical detail such as string length or string format in the case of an e-mail. None of them are a business rule, and none of them belong in the domain.

Transactions

In a typical business use case there are often multiple actions that need to succeed or fail together inside a transaction. By managing transactions in application services, you have full control over which operations that you request of the domain will live inside the same transaction boundary. This can be demonstrated using an updated RecommendAFriendService. Imagine the business has decided that if the referral policy cannot be applied, it should not create the new account. Therefore, the transactional boundary encapsulates creating the new account and applying the referral policy to both accounts, as shown in Figure 25.3.

images

FIGURE 25.3 Transactional boundary for Recommend-a-Friend.

Using the .NET Framework’s TransactionScope, you can add transactional behavior to the RecommendAFriendService, as shown in Listing 25-2. Not all databases, ORMs, and other frameworks use TransactionScope, but application programming interfaces (APIs) for creating, committing, and aborting transactions are usually similar.

Error Handling and Error Translation

Not all interactions with the domain will be successful. In such cases, the domain will likely throw exceptions or return error codes. These are cases in which domain validation fails (even though application-level validation was successful). An application service’s job is to handle these error conditions and translate them into suitable representations for external parties, so that no external parties are coupled to the structure of the domain errors. External parties could be either human users of a website or other software systems.

One domain error that can occur in the Recommend-a-Friend use case is when the referrer has a long-term outstanding balance. The business has decided that these customers should not be rewarded with a loyalty bonus. Because the domain will notify of this error condition, the RecommendAFriendService is responsible for handling the error and transforming it into a suitable external representation, as shown in Listing 25-3.

Listing 25-3 shows how application services can protect the domain structure by translating exceptions into a standard format. In this example, the external format is an ApplicationError exception. Using an ApplicationError exception prevents clients of the application service becoming coupled to the domain exception. Instead, clients are coupled to the ApplicationError, which is a more stable interface hiding the potential volatility of the domain.

Using a convention for representing errors, such as an ApplicationError exception, gives you the opportunity to handle all errors consistently. For example, in an ASP.NET MVC application, you can create a HandleErrorAttribute that handles ApplicationErrors. When the filter catches an ApplicationError, it knows that it was thrown by an application service, meaning that it is safe to present the message to the outside world. In contrast, when it catches any other type of exception, it does not know if it is secure to disclose the details of the error, so it has to return a generic error message, as shown in Listing 25-4.

Logging, Metrics, and Monitoring

Response times, errors, and other types of diagnostic information allow you to see how your application is performing and spot any potential issues at an early stage. But capturing this information can add unnecessary clutter to your domain logic and obfuscate important concepts. Sometimes you have to suffer this pain, but on many occasions, you can lean on application services to report this information instead.

Listing 25-5 shows an updated version of the RecommendAFriendService that logs the duration of each action and whether the overall result is an exception or a success so that the code does not have to be added to the domain model.

The key details to look for in Listing 25-5 are the usages of logger.Debug(), logger.Error(), and StatsDClient.Metrics(). Each of these method calls could have easily ended up mixed in with domain logic. Instead, each has been brought up into the application service.

Authentication and Authorization

A common infrastructural concern that most applications are forced to deal with is authentication. To demonstrate authentication in the Recommend-a-Friend scenario, this example models the case in which customer support is logging in to the admin interface and manually triggering the referral policy because there was a problem when the new customer signed up. Listing 25-6 shows how the AdminRecommendAFriendService checks whether the user is authenticated before it applies the referral policy.

For many applications, authentication isn’t a strong enough security measure, and authorization is additionally required. Authorization is the process that checks whether users have the appropriate privileges to carry out the requested action. Therefore, it’s an important tool in applications that have different types of users with different privileges.

Assuming that the AdminRecommendAFriendService is part of an application that contains a number of different users—customers and admins—authorization will be required to prevent normal users from applying the referral policy; business would be negatively affected if users could continually top up their online gambling account with free credits by just hacking a few uniform resource locators (URLs). (This does happen.) An upgraded version of the AdminRecommendAFriendService that performs an admin authorization check is shown in Listing 25-7.

Communication

Events that happen inside a domain from one bounded context may trigger events that are handled by other bounded contexts. You saw examples of this in Part II with the messaging and REST examples. It is the responsibility of an application service to transmit events between bounded contexts. This may involve publishing messages using a message bus (see Chapter 12, “Integrating Via Messaging,” for detailed examples) or an atom feed (see Chapter 13, “Integrating Via HTTP with RPC and REST,” for detailed examples), or other kinds of communication. Importantly, again, the goal is to decouple domain concepts from infrastructural concerns. Listing 25-8 shows an updated version of the RecommendAFriendService that also publishes events using NServiceBus.

Although Listing 25-8 shows an application service communicating via NServiceBus, the method of communication could be a remote procedure call (RPC), REST, or another. Application services should also be used to handle other concerns presented in Part II of the book, including handling external events, handling and publishing internal events (such as the messaging gateway in Chapter 12), and producing an atom feed.

Coordinating Full Business Use Cases

Looking back to the latest version of the RecommendAFriendService in Listing 25-8, you can see it handles a lot of infrastructural concerns that do not intrinsically add business value. They are only there to support the second major responsibility of application services that does add value—coordinating the domain model to carry out full business use cases. Listing 25-9 shows a section of an updated version of the RecommendAFriendService that builds on the infrastructural foundations by coordinating the domain to carry out the Recommend-a-Friend use case.

You can see the customerDirectory and RecommendAFriendPolicy variables being used in Listing 25-9. They represent domain objects and are used in this example to exemplify how an application service has to interact multiple times with the domain to carry out the entire use case. Here, it fetches the referrer, asks the domain to create the new customer, and finally tells the domain to apply the policy.

Application Services and Framework Integration

Application services are notorious for becoming bloated with loosely related logic that doesn’t appear to belong anywhere else. Sometimes chunks can be refactored into their own classes. In the RecommendAFriendService, Validate() is a good candidate for moving into its own class. Another source of bloat found in application services is boilerplate duplication. In the RecommendAFriendService, the transaction code is typical boilerplate code that could end up being duplicated in every application service. You could create a base application service that follows the Template Method pattern, or you could create a utility that takes a lambda and wraps it with a transaction.

Even with the patterns just mentioned, there is sometimes a cleaner solution than using all-encompassing application services. Many modern frameworks provide hooks for you to inject your infrastructural concerns. ASP.NET MVC is a good example. It has the concept of ActionFilters, which act like a pipeline. Using action filters, you can inject filters that handle validation, opening and closing transactions, logging, and other infrastructural concerns. Listing 25-10 shows an example TransactionFilter ActionFilter.

When the framework takes care of all your infrastructural concerns, the need for an application service is debatable. Instead, you may be tempted to put your few lines of domain model coordination directly inside a controller, as Listing 25-11 does.

As enticing as frameworks are, there is a big gotcha to be aware of. If you are using the application service in multiple places—perhaps a web front end and a desktop front end—then using an all-encompassing application service may prevent you from having to duplicate the infrastructural logic in both front ends. You need to make a per-project decision about how tightly you want to couple yourself to the frameworks you are using.

Domain Logic from an Application Service’s Perspective

A challenging activity for many DDD practitioners is drawing the line between application logic and domain logic. Infrastructural concerns are normally easy to identify, but the coordination logic that pieces together full use cases can sometimes appear to contradict the “No business rules in application services” principle. If you look back to Listing 25-9, you can see three interactions with the domain: fetching the referrer, asking the domain to create the new customer, and applying the policy. Some would argue that this logic should live in the domain.

One approach to deciding if a sequence of interactions belongs in the domain is to ask, “Should this always happen?” or “Are these steps inextricable?” If so, that sounds like a domain policy, because those steps always have to happen together. However, if those steps can be recombined in a number of ways, potentially it’s not a domain concept. In the Recommend-a-Friend use case, you might argue that the referral policy should only ever be applied to an existing customer (the referrer) and a new customer (the referrer’s friend). It follows, then, that the logic in the application service is actually a business rule that may be best encapsulated inside a domain service. However, sometimes the business wants to apply the policy to two existing accounts if there was an error during creation. In that case, the creation of the new account and the policy aren’t as coupled, suggesting this is application logic.

A sign that you have avoided leaking domain concepts into application services is that each interaction with the domain is expressive. An example of this is RecommendAFriendPolicy.Apply(). The implementation of this method credits each account with $50 and promotes the referrer to gold loyalty status. If those steps lived in the application service, it would be less expressive, more verbose, and a big sign that domain concepts had leaked out of the domain.

Application Service Patterns

You can use a variety of design patterns and principles inside application services based on your preferences and based on context. In the RecommendAFriendService from the previous section, you saw vanilla, script-like, object-oriented code. In many cases, that is the simplest possible solution and a good choice. On other occasions, you may find it presents maintenance headaches or undesirable coupling. The following patterns have arisen within the community for addressing concerns like these.

Command Processor

Using the command processor pattern, you can avoid developing large application services with many concerns. Instead, you have a command and a processor for each use case. Listing 25-12 shows a monolithic application service that can be considered to have multiple responsibilities.

Friction can occur when you have application services like the BloatedRecommendAFriendService in Listing 25-12. The implementations for each method may vary considerably, with each using different dependencies. Low cohesion is especially common in applications that use CQRS. Consequently, the application service can grow into a source of development friction. The command processor pattern can be used to alleviate these pains.

To create an alternative version of the BloatedRecommendAFriendService using the command processor pattern, the first step is to create a command that expresses intent and contains all the relevant information needed for it to be carried out. You can see an example of this in Listing 25-13.

After creating the command, you then create a command processor. An interface for a command processor that processes RecommendAFriend commands is shown in Listing 25-14.

Creating a command and processor as above is fundamentally just a case of creating a separate interface for each use case in the application, with the aim of isolating responsibilities and increasing expressiveness. But many DDD practitioners have taken the pattern further by adding a layer of indirection that provides looser coupling and the ability to chain command processors. Chaining can be used to create a pipeline that carries out infrastructural concerns such as validation, transactions, and logging, allowing you to isolate domain coordination. This version of the pattern requires common processor interfaces, as shown in Listing 25-15.

Each command processor then implements this interface so they can be chained together. First, you want to create a handler that is specific to the use case you are implementing. In this case, that would be a RecommendAFriendProcessor, as shown in Listing 25-16.

Generic command processors that you can apply to any use case also need to implement the ICommandProcessor interface. You can see demonstrative logging and transaction processors in Listing 25-17.

When you’re looking at Listing 25-17, the most important detail to discern is that each command processor takes another processor in its constructor. When a processor processes a command, it invokes the processor passed into its constructor. Essentially, it is wrapping (or decorating) the “child” processor to create a pipeline that can be infinitely long. Another important detail to be aware of occurs inside Process(). A processor can perform logic before and after the processor it wraps. As you can see with the TransactionProcessor, it starts a transaction, invokes the child (which may invoke other children), and then commits or rolls back the transaction depending on whether the wrapped processor threw an exception.

Wiring up command processors is the last remaining detail. You can see the simplest possible version of this in Listing 25-18. If you compare the code with Figure 25-17, it should help you see how the pipeline is being created.

There are other ways of wiring up pipelines than that shown in Listing 25-18. Some teams do prefer to manually wire up each pipeline in a similar fashion to this using a few helper methods to avoid duplication. Other teams prefer to use dependency injection. It’s completely up to you to decide how to configure your pipelines.

Publish/Subscribe

A pattern for looser coupling is publish/subscribe, whereby application services subscribe to events in the domain. You may want to consider this pattern when your domain logic is inherently event based, especially when you pass commands into the domain but do not receive a return value. An interface for an event-based version of the ReferralPolicy is shown in Listing 25-19.

To use the IRefferalPolicy in Listing 25-19, an application service passes a command into Apply() as usual. However, to learn whether the command was successful, the application service has to subscribe to the two events: ReferralAccepted and ReferralRejected. This pattern is illustrated in Listing 25-20.

Subscription to the IReferralPolicy’s events occurs in the RecommendAFriendService’s constructor shown in Listing 25-20. Anytime the domain model fires either of those events, the appropriate handler is called: HandleReferralAccepted() or HandleReferralRejected(). These events are triggered by passing commands into the domain model, as occurs inside RecommendAFriend().

A problem with the code in Listing 25-20 is that it cannot handle transactions properly because the event handlers do not have access to the transaction object to commit or roll back. A solution to this problem is to have an instance field as a transaction. For this to work, though, you have to ensure that a new instance of the RecommendAFriendService is created for each new transaction to avoid multithreading issues.

Another transaction-related problem with the code in Listing 25-20 occurs in multithreading scenarios. Consider the case in which the command is applied asynchronously in another thread using a C# task:


Task.Factory.StartNew(() => policy.Apply(command));

The transaction scope wrapping this call is no longer in scope when Apply() is called asynchronously. Fortunately, .NET’s TransactionScope has modes that help in async scenarios (http://stackoverflow.com/questions/13543254/get-transactionscope-to-work-with-async-await). When using transactions and threads like this, you still need to be careful. You may also want to consider using async/await.

Request/Reply Pattern

Before choosing patterns with a higher complexity trade-off, it’s always worth considering the simplicity of the request/reply pattern. This pattern follows the One Model In One Model Out Approach (OMIOMO), where a data transfer object (DTO) is passed into the application service and a DTO is returned, as Listing 25-21 demonstrates.

A common convention is to suffix the type of the input model with “request” and the output model with “response.” Though this is an optional aspect of the pattern, both objects should be simple DTOs that do not contain domain objects, and both should reside within the application services layer. The RecommendAFriendRequest DTO in Listing 25-22 and the RecommendAFriendResponse DTO in Listing 25-23 exemplify these characteristics.

Another common convention when using request/reply is for the response object to contain the status of the use case. When an error occurs or a policy is rejected, for example, instead of the application service throwing an exception, it will set the appropriate status on the response object.

Overall, request/reply is a simplistic pattern that relies on procedural code. If you don’t need the benefits of other complex patterns, it’s best to keep things simple and make life easier for other developers by using request/reply until it starts to cause friction.

async/await

C# developers can write single-threaded-like code that has the benefit of being asynchronous thanks to the async and await keywords added in C# 5. You should definitely consider them as an option if you want to build asynchronous, nonblocking applications. This pattern can conflict with your needs for a clear and expressive domain model, though, due to asynchronous methods requiring a return type of Task<T>. Listing 25-24 demonstrates this problem.

In Listing 25-24, you can see the customer directory returns Task<Customer>. This is so that an implementation can use nonblocking database calls that are more thread efficient and scalable. Clearly, this pollutes the domain a little. The conciseness of async and await does mitigate the syntactic noise, as the RecommendAFriendService in Listing 25-25 shows.

One benefit of making the three calls in RecommendAFriend() and not the domain is that the technical details of async/await do not clutter the domain logic, as Listing 25-25 illustrates. Also note in Listing 25-25 that RecommendAFriend() is marked with the async keyword. This ensures that when this method is called, it is executed asynchronously if it contains asynchronous calls. Both of the first two lines of code are in fact asynchronous calls; the await keyword signifies this. When the .NET runtime reaches the await keyword, it tries to avoid blocking threads waiting for the invocation to complete. Again, with this solution, you need to take a bit of extra care when working with transactions (http://stackoverflow.com/questions/13543254/get-transactionscope-to-work-with-async-await).

You have to weigh the pros and cons when using async and await. Their use can be more resource efficient, but they also add noise to your code. Fortunately, in Listing 25-25, most of the noise was confined to the application service and not the domain. This might be a compromise that you can aim for in your own designs, where possible.

Testing Application Services

You can cover large vertical slices of functionality or features by testing at the application service level. Ideally, the aim is to test as much as possible so that the environment is as live-like as possible. These types of tests can fall into a variety of groups, including system tests, acceptance tests, integration tests, and functional tests.

Use Domain Terminology

Tests can be a fantastic opportunity to express domain concepts by writing them in the UL. You may want to sit down with domain experts to create tests based on their acceptance criteria using Behavior Driven Development (BDD). This gives you the opportunity to verify with the domain expert(s) that the wording of your tests aligns precisely with domain concepts.

A high-level test outline for the Recommend-a-Friend use case testing at the application service level is shown in Listing 25-26. Notice how the names of the tests express domain concepts.

Test as Much Functionality as Possible

Testing as much as possible increases your confidence that everything will work together when the application is deployed. To do this, you need to avoid using mocks and stubs, preferring to use concrete implementations instead. Listing 25-27 shows how this applies to the high-level Recommend_a_friend test.

In Listing 25-27, the RecommendAFriendService is being constructed with a concrete implementation of the CustomerDirectory. This is the same implementation to be used when the application is deployed. You can see how this test will validate that the CustomerDirectory repository and RecommendAFriendService application service are integrating as required.

You will also notice in Listing 25-27 that the CustomerDirectory is constructed with an in-memory database. This is because it is not possible to test against a real database in a test easily and quickly. Instead, the database is replaced with an in-memory version that is easy to set up and fast to test against. This isn’t completely live-like, though, so there’s still a small chance the test may pass when something database related may fail at run time. Adding a few full end-to-end tests to your suite that do hit a real database can improve your confidence.

Another component that cannot be tested easily and quickly is the emailer, so it is stubbed using RhinoMocks. This means that this component is not being covered; this test will succeed as long as methods on the emailer are called with the correct arguments, as shown in Listing 25-28.

RhinoMocks provides a method called AssertWasCalled() that throws an exception if the passed-in lambda was not invoked during the test run. In Listing 25-28, therefore, RhinoMocks throws an exception if the emailer’s SendReferralAcknowledgement() was not called with an argument that represented the referrer. As mentioned, though, mocks and stubs are often a last resort, when you cannot test the full functionality. When you can test the full functionality, you can directly test the expected outcome, as shown in Listing 25-29.

The Salient Points

  • Application services and a service layer allow you to isolate technical concerns from domain logic.
  • Technical concerns include transactions, database connections, and e-mails.
  • Application services are responsible for coordinating with the domain to carry out full business use cases.
  • When communicating with the domain, application services should invoke expressive high-level APIs on domain objects.
  • An important responsibility of application services is to protect domain structure by presenting higher layers and external components with more stable interfaces to couple themselves to.
  • You can use design patterns like the command processor pattern and asynchronous patterns in the service layer.
  • Testing application services is an opportunity to express high-level behaviors or full business use cases, in the ubiquitous language, while covering a high percentage of the implementation.
..................Content has been hidden....................

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