'What is the best practice to construct a Domain Object depending on external calls?
I am trying to build an object that gets its data from external APIs. I will try to explain it with an example:
First of all, I get a POST body in my API to create an object:
class CarRequestBody {
private String colorId;
private String engineId;
//Constructors, etc.
}
Then with this info I need to build a detailed instance of the Car. Feeding the data from external services:
ColorDetails color = colorApiClient.getColor(colorId);
EngineSpecifications engine = engineApiClient.getEngine(engineId);
And finally I build my Domain Object with all this info.
So I would like to know what is the best practice in order to build the instance. I have thought in 3
different ways:
1 - A method in CarService like this:
public Car createCar(CartRequestBody body) {
ColorDetails color = colorApiClient.getColor(body.getColorId);
EngineSpecifications engine = engineApiClient.getEngine(body.getEngineId);
Car car = new Car(color, engine);
}
2 - Feed the data in the constructor:
public Car(CarRequestBody body) {
this.color = colorApiClient.getColor(body.getColorId);
this.engine = engineApiClient.getEngine(body.getEngineId);
}
3 - In the getters of the domain class:
class Car {
private ColorData color;
private EngineSpecifications engine;
//Constructor
public ColorData getColor() {
if (color == null){
return colorApiClient.getColor(colorId);
}
return this.color;
}
....
}
Is there some design pattern for this scenario?
Solution 1:[1]
If you aim to keep your design clean, your domain class Car
should be aware only of the objects like ColorData
and EngineSpecificationsonly
that it requires to function properly.
It's not a good idea to inject apiClient
into the Car
class because it's unrelated to its functionality and will increase coupling.
Also, what if an API call will fail, do you really want to add the plumbing code that will handle this scenario into a constructor?
Similarly, I don't see an advantage in defining a constructor that expects CarRequestBody
.
The cleanest approach will be to keep the constructors of your domain classes simple and free from objects that are not needed for them to act. And if you want to generalize the process of instantiation of these domain classes, you can introduce utility methods that will take care of it.
Solution 2:[2]
I would suggest using the Builder design pattern.
Here you can see the code-
Car.java
public class Car{
private ColorDetails colorDetails;
private EngineSpecifications specifications;
private Car(CarBuilder builder){
this.colorDetails = builder.colorDetails;
this.specifications = builder.specifications;
}
public static class CarBuilder {
private ColorDetails colorDetails;
private EngineSpecifications specifications;
public CarBuilder withColor(ColorDetails colorDetails) {
this.colorDetails = colorDetails;
return this;
}
public CarBuilder withSpecifications(EngineSpecifications
specifications) {
this.specifications = specifications;
return this;
}
public Car build() {
Car car = new Car(this);
return car;
}
}
}
// Client code
public class Client{
ColorDetails color = colorApiClient.getColor(colorId);
EngineSpecifications engine = engineApiClient.getEngine(engineId);
Car car = new Car.CarBuilder()
.withColor(color)
.withSpecifications(engine)
.build();
}
Solution 3:[3]
What would be wrong with a simple constructor? I don't know Java, but would be this in C#:
class Car {
...
public Car(Color color, Engine engine) {
... // set properties with the parameters
}
}
Oh, and if you want to do some Decoupling, please don't use the same objects in your Domain as in API. So you will need some kind of Mapper object.
// EDIT
Since you are using a request, you could make a requesthandler class. If you use Inversion of Control or a Mediator you could connect the request to the handler in that way. So you could look into those design patterns. You could also look into Clean Architecture to separate your domain code from external systems (API's in this case)
public CarRequestHandler : IRequestHandler<CarRequest> {
public CarRequestHandler(IColorRepository colorRepository, IEngineRepository engineRepository) {
this.colorRepo = colorRepository; //etc.}
public Handle(CarRequest request) {
// call repositories with the ids and create the domain object and stuff
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 |