0

Short question.

From DDD perspective (or just maintainable architecture and common sense), where should I put a layer of business logic that has no domain objects to operate with but does some "smart" data preparation and orchestrate a bunch of side-effect calls? By "smart" I mean that it has some rules and operations` order restrictions that go beyond just building the DTO. The naive implementation of calling methods with data preparation before each one looks messy and the situation gets worse as logic gets more complicated. Also, such implementation is untestable without "mocking the entire universe".

A long and concrete example.

I have an order Web App that communicates to Order API. My Web App has an operation of updating order position in the order throw API. The implemented method which takes data from the front end and saves it consists of three stages:

  1. Delete existing position
  2. Add new positions
  3. Re-apply discounts

This is an odd implementation of the Order API and I have no control over it. The code looks like this

public async Task SaveOrderPosition(PositionViewModel positionViewModel)
{
    var deleteDto = _dtoBuilder.BuildForDeletion(positionViewModel);
    var deleteResult = await api.Delete(deleteDto);
    if(deleteResult.IsFailure)
    {
        throw new Exception();
    }
    var addDto = _dtoBuilder.BuildForAdding(positionViewModel); //generates some new ids and data. the "smart" part
    var addResult = await api.Add(addDto);
    if(addResult.IsFailure)
    {
        throw new Exception();
    }

    var discountsDto = _dtoBuilder.BuildDiscounts(addDto, positionViewModel); //also "smart" part as it has dependency on added data
    var discountsResult = await api.ApplyDiscounts(addDto);
    if(discountsResult.IsFailure)
    {
        throw new Exception();
    }
}

As I see this code is a) untestable with unit tests, b) some important logic lays in dto builder, c) it will get only messier as new rules emerge or API is changed. (Maybe I am wrong and it is fine)

My thoughts

So here I don't see any Domain Model objects to use to make code more testable and extensible.

  1. First thought - build order object and do something like
order.UpdatePosition(position);
await Save(order);

But creating the whole Order object is an expensive operation and the data other than from ViewModel actually is not necessary for the Save operation

  1. (Stupid one) Implement position domain model.
public async Task SaveOrderPosition(PositionViewModel positionViewModel)
{
    var position = new Position(positionViewModel); //actually creates inconsistent objects
    position.Save(); 
    await Save(position.Events);
}

class Position
{
    public List<PEvent> Events {get;}

    public void Save()
    {
        Events.Add(new DeleteEvent());
        // do some "smart" staff
        Events.Add(new AddEvent());
        // do some discounts staff
        Events.Add(new ApplyDiscountEvent());
    }
}

The good part is that some business logic is decoupled from the application layer and is testable. But nor position model (as it has irrelevant state after construction) or its Save method has no real-world sense as position does not save itself. As I see it

  1. Create a domain service. Looks like what I need - the thing that holds operations that are not part of any entity. But it is also said that domain service should operate on several aggregates, I have none of them. Also, what should the method of such service return in this case? Bunch of events or something like a tuple of DTOs to use in API calls?

So actually I am struggling from moving the code from a procedural way to an object-oriented and DDD way as no objects in my domain have the responsibility of required operations. (Or I just don't see one)

shumantt
  • 23
  • 5

1 Answers1

1

Actually, according to KISS and YAGNI you should keep your code as simple as you can and do not overengineer so if you have only 3 use cases that you described, I think the current way is fine. I believe you can wrap the calls to DtoBuilder with unit-tests.

In case you want it to be more structured I would suggest you considering approach with Anemic Domain Model as it seems that you have pretty simple and straightforward business rules. In this approach, your domain entities are very simple (they are similar to dtos) as they do not contain any business logic, but only getters and setters. And all the business logic goes to service methods. Here you can unit-test the calls to you service methods.

As to Rich Domain Model (with aggregates, logic inside entities and other stuff):

  1. aggregate is not just an entity with behavior, but the entity that controls the transactional boundaries and data consistency inside itself, underlying entities and value objects. That said if it seems that you have simple aggregate it is completely fine
  2. you are not obliged to have more than one aggregate to move business logic to domain services, but you can (this depends on what you think will work best for you, it is not a law) do that if the logic inside entity does not look organic (here I also prefer to consider data consistency: if data is still consistent than I can move logic to domain service)