DDD – Is Putting Logic in API Gateway Common or Acceptable?

domain-driven-design

Let's use https://github.com/dotnet-architecture/eShopOnContainers as an example. There are CatalogService, BasketService and there is ApiGateway. In api gateway in BasketController there is AddItemToBasket handler (https://github.com/dotnet-architecture/eShopOnContainers/blob/dev/src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/BasketController.cs#L123) which first calls catalog service to get catalog item (by catalog item id from request), then gets or creates basket and then it updates basket with new item added, checking if items with this id has not been already added to basket, and if it has then it only updates quantity of that item in basket.

So quite amount of "business logic" happens in this controller. I know that eShopOnContainers is not strict DDD example, and I wanted to know if this particular pattern/example is "acceptable" when you follow DDD principles?

What I noticed is that it simplifies (and decouples it from catalog service) basket service because it does not have to call catalog service by itself. On the other hand there is some logic (specifically updating quantities instead of adding item which already exists in basket) that for sure should be implemented in basket service and whole basket should (I think) be implemented as aggregate.

So from DDD point of view basket service should receive the request to add new items, then call (asynchronously through event broker would be the best) catalog service and update the basket. Or eventually basket controller could call catalog service to get items from catalog and call basket api to add those items.

While I know that basket service is simplified in this repo/example, I wonder if it's common approach, regarding DDD, that if there is service A that needs some kind of read model from service B to make decision (like basket service need some data from catalog service) then instead of calling (even asynchronously) service B from A, API gateway first calls service B to fetch some read model from it and then it calls service A passing this read model / required data directly to it?

Best Answer

My take on this is that the "ApiGateway" services are acting as a "backend for front end" and converting between the front ends ViewModels and the back ends Models with some simple logic. You could add a service layer, but its unimportant to the overall architecture.

You could just as easily do this kind of "check the item Ids are valid" logic in the front end and connect direct to the back-end services, but you cant do it in the back-end services without coupling them.

The basket service accepts the itemId as an external Id from another system and it doesn't care if that other system has the item or not. Adding that kind of coupling is something that they have chosen not to do when they choose the aggregate roots for the system as a whole and you wouldnt want to add it back in for fear of ending up with one massive service, rather than multiple microservices.

This choice is a key factor in DDD and questions such as:

  • "what do i do when i need to actually deliver the items in a basket when the catalog doesn't know what they are?"
  • "how can i ensure the basket the customer is putting together only includes items from the catalogue?"

Are left to higher layer "Delivery" and "WebShop" Domain Services or Objects. You have to be careful to keep your services hierarchical, loosely coupled and avoid circular references.

A common pitfall for example would be to add a ICatalogue reference to the Basket service and then later add a IBasket reference to the Catalogue service. Everything would compile, but in reality you wire up one service to the other and have potential infinite loops, or at least inefficient calling back and forth and your choice of ARs is called into question.

Related Topic