1

I have an application that gets a car entity from a third party database. I call the entity ThirdPartyCar. My application needs to create a Car entity by using data from a ThirdPartyCar. However, the Car entity must also derive some of its data from my application's database. For example, a status of a ThirdPartyCar might be _BOUGHT and through a database lookup my application must transform to Sold.

I currently have a Car constructor that has a ThirdPartyCar argument. But the Car constructor cannot populate the lookup data since it is an entity and entities should not have a reference to a repositories. So, I also have a service to populate the remaining data:

public class ThirdPartyCar {
    @Id
    private Long id;
    private String vin;
    private String status;
    // more props + default constructor
}

public class Car {
    @Id
    private Long id;
    private String vin;
    private CarStatus status;
    // more props (some different than ThirdPartyCar) + default constructor

    public Car(ThirdPartyCar thirdPartyCar) {
       this.vin = thirdPartyCar.getVin();
       // more props set based on thirdPartyCar
       // but props leveraging database not set here
    }

 public class CarStatus {
    @Id
    private Long id;
    private String status;
 }

 public class CarBuilderService {
     private final CarStatusMappingRepository repo;

     public Car buildFrom(ThirdPartyCar thirdPartyCar) {
        Car car = new Car(thirdPartyCar);
        CarStatus status = repo.findByThirdPartyCarStatus(thirdPartyCar.getStatus());
        car.setStatus(status);
        // set other props (including nested props) that depend on repos

     }
  }

The logical place to create a Car based on a ThirdPartyCar seems to be the constructor. But I have a disjointed approach b/c of the need of a repo. What pattern can I apply such that all data is created in the constructor but still not have the entity be aware of repositories?

user8297969
  • 415
  • 1
  • 6
  • 18

1 Answers1

1

You should avoid linking two POJO classes from different domains in constructor. These two classes should not know anything about each other. Maybe they represent the same concept in two different systems but they are not the same.

Good approach is creating Abstract Factory interface which will be used everywhere where Car should be created from ThirdPartyCar:

interface ThirdPartyCarFactory {

    Car createNewBasedOn(ThirdPartyCar source);
}

and one implementation could be your RepositoryThirdPartyCarFactory:

class RepositoryThirdPartyCarFactory implements ThirdPartyCarFactory {

    private CarStatusMappingRepository repo;
    private CarMapper carMapper;

    public Car createNewBasedOn(ThirdPartyCar thirdPartyCar) {
        Car car = new Car();
        carMapper.map(thirdPartyCar, car);

        CarStatus status = repo.findByThirdPartyCarStatus(thirdPartyCar.getStatus());
        car.setStatus(status);
        // set other props (including nested props) that depend on repos

        return car;
    }
}

In above implementation you can find CarMapper which knows how to map ThirdPartyCar to Car. To implement this mapper you can use Dozer, Orika, MapStruct or your custom implementation.

Other question is how you got ThirdPartyCar object. If you load it by ID from ThirdPartyRepository you can change your abstract factory to:

interface CarFactory {
    Car createNew(String id);
}

and given implementation loads by ID ThirdPartyCar and maps it to Car. Everything is hidden by factory which you can easily exchanged.

See also:

Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • This is very helpful. Would it make sense to move something like setting the car status to a [custom converter](http://dozer.sourceforge.net/documentation/customconverter.html) and registering that with the mapper? I'm not sure how a custom converter would work with Spring DI and beans such as a repo. – user8297969 Mar 27 '19 at 20:51
  • 1
    @user8297969, I think you can do that this way. This is just another layer. Of course you need to handle exception and corner case bug generally this is a good idea. Take a look on [Spring Framework Integration](http://dozer.sourceforge.net/documentation/springintegration.html) and [How to use Dozer with Spring Boot?](https://stackoverflow.com/questions/28369433/how-to-use-dozer-with-spring-boot). These two should be good start points for further investigation. – Michał Ziober Mar 27 '19 at 20:58
  • Thanks. I've been reading a bit more. MapStruct also seems quite good. The implementation of `CarFactory` will use a lot of repos to set `Car` data. A good example of one such use of a repo was for setting `CarStatus`. I need to think about how to avoid `CarFactoryImpl` from having so many repo dependencies and therefore too much responsibility. – user8297969 Mar 28 '19 at 15:15
  • 1
    @user8297969, you can use [Chain of Responsibility](https://sourcemaking.com/design_patterns/chain_of_responsibility) and link factories in a chain of factories: `status factory -> type factory -> engine factory`, Now, each factory does only one thing and you can register in Spring which one is entrance to this chain. You can also easily define what is in chain and what is not. Other factories does not know anything about other pieces of car and `Car` is decoupled from how it is created. – Michał Ziober Mar 28 '19 at 15:21
  • Interesting; I guess all links in the chain would need to get a `ThirdPartyCar` and a `Car` in its handle method. And that method could return `Car` or void. – user8297969 Mar 28 '19 at 17:01
  • @user8297969, For sure it should have `Car` and some `Context`. `Context` could handle all required extra fields which factories could use. Of course it depends from what is needed. Try to keep context small. `ThirdPartyCar` - I am not sure about it. Of course it could have it byt I would try to avoid it somehow. Hard to tell without knowledge about project. – Michał Ziober Mar 28 '19 at 22:20
  • Thanks. Makes sense that `Car` is needed because the factory (such as `EngineFactory`) will have to set the engine on the `Car` object being built &pass it on via the `nextFactory.handle`. I'll have to think more about how to pass only some `Context` instead of a full `ThirdPartyCar`. I'm not sure right now how chaining could operate in any order when only passing some `Context`. `TypeFactory` could call `nextFactory.handle(car, context)` but it has no way of knowing who is getting that call and thus what context (`i.e. ThirdPartyCar` data) it needs. I'm probably missing something though. – user8297969 Mar 28 '19 at 22:40