2

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 Answers 2

4

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);
        }
    }
    
5
  • The external API also returns an external identifier I want to store in my database to link database and external API information. You still going for solution #1? Oct 24, 2022 at 7:01
  • @TheEmpire12: I was going for solution 2. That enforces the order calls get made, which is what you need in this case. Sorry if I didn't make that clear. I just wanted to compare solutions 1 and 2. Oct 24, 2022 at 11:30
  • I also just realized I typed "1" instead of "2". I corrected that. I was thinking one thing and typed another. Oct 24, 2022 at 11:36
  • I think the code you posted is another solution. In that solution, the DatabaseExternalApiProxy is an infrastructure class (or it's domain? I am confused because the class implements a domain interface) but with reference to 2 domain interfaces? That confuses me a little bit (I am new developing hexagonal) With number #2 I refer this: In the useCase function, do useCase stuff, call one repo, then call the other one. Both repos are called from application layer. Oct 24, 2022 at 21:07
  • @TheEmpire12: there is nothing wrong with an infrastructure class implementing a domain interface. In fact, if you need to do infrastructure stuff, but keep your domain logic ignorant, then infrastructure classes must implement domain interfaces. Mar 21, 2023 at 19:44
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
  • Thanks for your answer. Viewing it as a cache clears me some points. Maybe if I want consistent data I could not have that cache, and if I really need it I need to do that stuff about creating 2 different methods... I don't like it so much Oct 24, 2022 at 21:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.