phpservicedomain-driven-design

ddd - Where should synchronization with remote API go?


In order for my application to work, I need to synchronize regularly data from an outside service (could be an API, or a simple text file, but for now it is an API).

Since this would require creating / updating many entities at once, I need to create a Domain Service. However, I also need to create some DTOs that will contain the response of the remote API right?

Where should this logic go? Should I have the following directory structure:

Domain -
    Model - // my set of entities and repository interfaces are here
        ....
    Synchronization -
        RunSynchronizationService.php // domain service
Application
    Synchronization - 
        SynchronizeData.php // application service
        SynchronizationDataSourceInterface.php // used by application service
        MySpecificRemoteApiDataSource.php // this implements the interface above
        SynchronizationDataSourceResponse.php // this would be returned by each call of  SynchronizationDataSourceInterface method, and would contain data normalized, but not validated.
Infrastructure -
    MyConcreteImplementationOfModelEntityRepository.php   

And when I want to synchronize the data, I simply call Application\Synchronization\SynchronizeData's sync method, which will take a concrete implementation of SynchronizationDataSourceInterface, call its methods, and validate the returned SynchronizationDataSourceResponse objects before transferring them to Domain\Model\Synchronization\RunSynchronizationService?

Or should I remove RunSynchronizationService (the Domain Service) and let the Application Service (SynchronizeData.php) create / update the domain entities at each step of the synchronization process?


Solution

  • Generally when presented with the question as to where an external service interface should live, I try to think of it in terms of it being another repository. The level of abstraction I choose here will depend highly on who will use the service (just the domain/app layers of this project? other projects?), if there are different versions/vendors of the service, and how which service to use is determined.

    For your example, I'm going to assume that this application is the only one using the sync service directly. Shared services require common end points (such as the interface and output objects) to be isolated even further to avoid spilling unnecessary objects into other projects.

    But in this case, to treat the service as another repository, I'd place the interface for it and standard output the domain expects and uses in the domain.

    What is meant by "validation" is a little vague in your description, so I'll try to attack the different views of that here. One or all may apply.

    If validation requires you to compare against domain data, it should probably reside in the RunSynchronizationService.php module. That module appears to be responsible for taking the sync data and applying it to your domain.

    If validation is on the data coming back from the sync service call, and doesn't require direct access to the domain object graph, I would put that validation on the service implementation of the interface, and expose that validation call on the service interface. To handle the situation of that validation being the same across several versions of the sync service (such as VersionA, VersionB, etc), you can use inheritance and overrides, or common helper functions to the sync service, etc. In my example below, I used inheritance.

    It could be you need to do both kinds of validation. First check for sync data problems agnostic to the domain (on the implemented classes), and then against business rules in the domain (in RunSynchronizationService). But likely both of those calls would occur in the RunSynchronizationService as you'd expose the sync data validation call on the interface.

    The application layer should be responsible for creating the instance of the service (MySpecificRemoteApiDataSource), and passing it into the RunSynchronizationService.php module as SynchronizationDataSourceInterface. If there are multiple versions, the application layer would likely be responsible for choosing which one (from a configuration, perhaps), and using a factory.

    But this again highly depends on the scope of that sync service. If you have external projects relying on it, you might want that factory part of the service layer itself so each of the other projects operate with the same choosing method.

    Domain -
        Model - // my set of entities and repository interfaces are here
            ....
        Synchronization -
            RunSynchronizationService.php // domain service
            SynchronizationDataSourceInterface.php // used to define the contract associated with a sync service
            SynchronizationDataSourceResponse.php // this would be returned by each call of  SynchronizationDataSourceInterface method, and would contain data normalized, but not validated.
    Application -  
        Synchronization - 
            SynchronizeData.php // application service - Uses a factory or some means of determining which version to use and introduce the domain to the data point.
    
    Infrastructure -
        MyConcreteImplementationOfModelEntityRepository.php  
    
    Synchornization -
        VersionA -  
            MySpecificRemoteApiDataSource.php  // Implements SynchronizationDataSourceInterface.php, inherits from SyncApiDataSourceBase.php
        SyncApiDataSourceBase.php  // Common logic for sync goes here, such as validation.