Help implementing call to external API and store summary in my database in hexagonal architecture with DDD

  softwareengineering

0

Good afternoon.

My situation is this:
I have to call an external API to create some data and store it there, but I want to have a summary of that data in my own database. I have my use case that creates the AggregateRoot and my port interface that the implementation calls the external API. Where should I put the database store code? I have thought these options:

  1. Encapsulate the database repository port in the external API adapter implementation and call
  2. Call the database port in my use case so after the external API call succeed, I can call that port and store the information.
  3. In the external API adapter, when the external call is succeed, send a event with the information created and create another use case to store the information in my database.

Number 1 and 3 seems quite strange to me because I will be manipulating domain objects in the infrastructure layer.
I think number 2 is the way to go, but I am not sure.
Thank you!

2

Encapsulating data replication in the API adapter would be nice, because your use case does not need to remember to do this. Error handling and order of operations (call API first, then insert into DB) get pushed into the abstraction provided by the adapter. Replicating this data is easy and hidden from the perspective of the caller, but this might be a little surprising. Sometimes “hidden” operations are not desirable for the simple fact they are hidden.

Replicating this data explicitly in the use case is nice, because that logic is right in front of your face. It becomes very easy to understand that we duplicate some data, but it becomes possible for a careless programmer to flip the order of operations in the future. Without understanding the system properly, someone could save data to the database first, and then call the API, only to have the API call fail. Now you have data in your database, but not in the external API.

I would lean towards choosing the solution that guards against accidental bugs in the future caused by changing the order methods are called. These can be excruciating to debug in production, because the failure isn’t obvious. Based on this, I would go for solution #2, because it solves “order of operations” issues. You do have a couple of options that might make the code easier to understand:

  1. Use one class that makes explicit calls to the API and then database.

    The benefit of this approach is seeing both calls right in front of your face.

    The drawback is you are coupling both operations together in the same class.

  2. Create two classes that implement the same interface, making the database class a proxy for the API class:

    IExternalApi api = new DatabaseExternalApiProxy(new ExternalApi(), repository);
    
    api.SomeOperation(data);
    

    The proxy class can juggle the order of operations, and you get loose coupling to the external API:

    public class DatabaseExternalApiProxy : IExternalApi
    {
        private IExternalApi api;
        private ISomeRepository repository;
    
        public DatabaseExternalApiProxy(IExternalApi api, ISomeRepository repository)
        {
            this.api = api;
            this.repository = repository;
        }
    
        public void SomeOperation(data)
        {
            api.SomeOperation(data);
            repository.ReplicateData(data);
        }
    }
    

4

0

If I understood your use case right I see some advantages in option 1.

In my understanding you want to mirror the API’s data as a sort of cache. (If this is not the case you can stop reading here and go with @greg-burghardt)

In this is the case I would indeed save this replication in your repository implementation – before your use case even is in contact with the data (option 1).
The question here is what kind of requirement is the replication of the API’s data? A) Is it a infrastructural (speed, availability,…) requirement or B) is it one of your domain? As of your description I guess it is more of an infrastructural requirement.

From an implementation pov I see these options:

  • Option 1A: Hide the cache completely from your use case and implement it in the repository
  • Option 1B: if your use case has knowledge about the consistency of this data then define the repository port accordingly. prepare different methods for the different situations your use case has to handle. this could be getDataConsistent() and getDataCached() and use the right one for the right situation.

All in all I think this is highly dependent on your domain requirements. I could completely confirm with gregs answer here but wanted to share a second point of view on that. Maybe it helps.

1

LEAVE A COMMENT