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:
- Delete existing position
- Add new positions
- 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.
- 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
- (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
- 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)