Archange
Archange

Reputation: 519

DDD - ApplicationServices Unit test

After reading Uncle Bob blog and TSS id DEAD article, I wanted to code some more tests.

My application is using layers : ApplicationService -> Model -> Support.

The application service defines the use cases. It's a bit procedural code (loadX, call X.doSomething, saveX). In some cases, the appService class needs lots of external dependencies.

For example, I have an object "D" used at the end of a "workflow". So when working with it, I need to check some rules on the objects "A" / "B" / "C" linked to this one. Say we have 3 entities related, each one with its own repository and in very special use case I need to check another rule deeper (like "AA" == another repository).

My appService will have something like 8 to 10 dependencies (5 repositories + some business services inter-entities). Testing that is not my "forte". I lack courage when mocking too many services. If each service has 3 methods but my appService only need one, then I need to know which method to mock or mock/stub all the methods (and mocking/stubbing 2/3 of useless methods).

There's a lot of problems I think. My first one is too many dependencies. I just don't want to split the dependencies and hide them under the carpet. Surely the domain entities are perhaps problematic too : bad boundaries, too much separation, etc.

After reading an article (uncle bob), I was thinking : I'm doing it wrong. perhaps the appService will need to express / declare an interface with WHAT it needs. But how do name it ? how to split it ? I'm not sure it's a good idea to put no related services together. Write something like a facade and tests will become easier (I now know that my class is coupled to theses externals services and I know exactly which ones).

thanks

Update 1 (23.06.2016) : For exemple, here for an application service class DPAppService the list of its dependencies :

DPRepository dpRepository;
RechercheFournisseurQueryService rechercheFournisseur;
RechercheFactureQueryService rechercheFacture;
DateService dateService;
SFRepository sfRepository;
CommandeRepository commandeRepository;
RepartitionBudgetRepository repartRepo;
GenerateurRepartitionsDPService genRepartDpService;
SaisieRepartitionsSurDpQueryService             saisieRepartitionsSurDpQueryService;
ImputationBudgetaireService imputationBudgetaireService;
NomenclatureService nomenclatureService;
ComptaGeneraleService comptaGeneraleService;
OperationInfoService operationInfoService;
DemandePaiementCalculateurService dpCalculateurService;
CodeMarcheAnnualiseRepository codeMarcheAnnualiseRepository;
DemandePaiementLigneFactory dpLigneFactory;
EcritureRepository ecritureRepository;
BrouillardNonViseRepository brouillardNonViseRepository;

quite scary, quite annoying to inject using constructor injection. There are some repositories (too much I think and the DP service only need a subset of the methods exposed by the repositories), some services (computation that can't be really hold by my domain at the moment).

I need to explain a little bit more about the domain. This class is responsible of all the usecases (20 methods, with some methods only a different flavor of another) attached to the "DP" concept. The concept is at the end of "workflow" (term used loose here) :

  1. first a user creates an "EJ"
  2. then he creates a "SF"
  3. then he creates "Facture"
  4. finally he creates a DP which binds together the previsous objects so we need to check / load for some usecases one of the previsous object. A DP cannot realy hold all the previsous objects (a bit too much data).

Ideas :

Any pros / cons / ideas ?

And thanks a lot for your time / answers.

Upvotes: 3

Views: 1609

Answers (2)

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57214

If each service has 3 methods but my appService only need one, then I need to know which method to mock or mock/stub all the methods (and mocking/stubbing 2/3 of useless methods).

Yup, that's a familiar code smell; or the other way around -- you've got working tests, but you have to keep going back to them to stub out another method in order to keep the tests compiling....

After reading an article (uncle bob), I was thinking : I'm doing it wrong. perhaps the appService will need to express / declare an interface with WHAT it needs.

Yes. The app service declares some flavor of service provider interface, which gives it an implementation agnostic way to describe the context that it needs to run in.

If it helps, this is analogous to the use of Repository or Domain Service interfaces in your domain model.

But how do name it ?

Naming things sucks:

  • Context
  • Client
  • Connector
  • Gateway
  • Seam

how to split it ? I'm not sure it's a good idea to put no related services together.

I've started thinking about it like aggregate design. By this I mean; we normally start out thinking of aggregates by imagining the structure of the entities in our model. But structure is a really static thing; what matters in the domain model is not that two bits of state are connected by structure, but that the changes to those bits of state are connected by business rules.

What does this translate to at the application service level? That is to say, if the application service is supporting several use cases, what does that tell us is common to those use cases? There ought to be some motivation beyond "it was easy to implement that way". I don't have an answer here myself, but I think it's in the direction of the right question to ask.

How do you test your appServices ? (unit or integration test only ?)

Both, ideally. The module in which the application services live have tests where the tests themselves act as service providers. The integration check wires together a number of modules to provide the services, then runs an independent series of tests against the assembly.

Do you think splitting the injected services ("facade" like or something more business or something else) is a good approach ?

Yes, but it's not without cost. The facades themselves are great for documenting what's actually going on. But each facade requires at least one implementation, likely more (test stubs and the like). You are going to pay a cost for that somewhere; more complex wiring in your modules, hunting to figure out where the implementation of some service actually lives, trying to find name classes/packages/modules so that you can keep everything straight, etc.

Upvotes: 1

bstack
bstack

Reputation: 2528

We have encountered this issue many times in the past, where we have workflow-like logic, and as a consequence we have many dependencies.

The way we tackle this is by using patterns such as pipeline/chain-of-responsibility pattern. So say if there is a workflow that has 10+ dependencies, we divide up each stage of the workflow into a separate domain service e.g. the following services would exist in a workflow for checking bank details are valid:

initial-validation
check-name
check-account
check-for-fraudulent-behaviour
event-publication
result-builder
save-result

Each of these domain services would implement the following interface:

using System;

public interface IService
{
    bool CanExecute(
        IContext subject);


    Context Execute(
        IContext subject);
}

Note that some domain services may or may not execute e.g. if initial-validation failed, there would be no need to check name. However in save-result, it will always execute whether success or failure

So then the workflow you are trying to test, instead of getting injected with 10+ dependencies it simply gets injected with a collection (pipeline) of domain services.

This pattern is excellent as you still get the ability to link code together to create workflow logic, however the testability, extensibility etc of the code is much greater.

In terms of testing, each domain service implementation can easily themselves be tested at a unit level and each one may have dependencies of their own such as repositories /external services etc. The workflow itself is easy to test also as now you just need to mock up the pipeline behaviour you want - which is relatively straight forward.

We often would the likes of a pipeline builder to aid with the testing of pipelines so pipeline building code can be reused.

Lastly, the Context object that we use in each domain service implementation is what links all domain service data structures together. Each result for each domain service is carried in the Context, see below for an example:

public interface IContext
    {
        InitialValidation.IResult InitialValidationResult { get; }
        CheckName.IResult CheckNameResult { get; }
        CheckAccount.IResult CheckAccountResult { get; }
        CheckFraud.IResult CheckFraudResult { get; }
        EventPublish.IResult EventPublishResult { get; }
        ResultBuild.IResult ResultBuildResult { get; }
        Save.IResult SaveResult { get; }
}

Long-winded answer, but its hard to describe in just a few paragraphs. In a nutshell, divide the workflow into a collection of ordered domain services. This simplifies the dependency structure and make the code easier to test.

Upvotes: 0

Related Questions