I am creating a Spring boot application and I need help with the design of my services. I will use a bike service as an example. Let's consider these endpoints:
/api/bike/v1/info
/api/bike/v1/find
The endpoints will call the Bike service, which can potentially call different providers such as BikeProvider1, BikeProvider2, etc. The providers will be specified in the properties of the application, this allows to change the provider without restarting the application.
structure example
+ bike
+ Client
- BikeRequest.java
- BikeResponse.java
+ domain
- Providers.java //providers enums
- ServiceType.java // service type enums
+ Exceptions
+ Controller
- BikeController.java
+ Service
+ Providers
- BikeProviderService.java //interface
- BikeProbider1ProviderService.java // implements interface
- BikeProbider2ProviderService.java // implements interface
- BikeService.java //interface
- BikeServiceImpl.java
Properties.file
bike.provider.primary: BikeProvider1
bike.provider.fallback: BikeProvider2
I also think that in the future I want to have 2 providers one as a fallback in case the first one is not responding. But for now, I will only use the primary.
My current design is this, I have enum classes for my service type and Providers. In the BikeServiceImpl.java I dynamically find the provider that I need and make the call.
*******************ENUM CLASSES **************************
public enum Provider {
BIKEPROVIDER1 ("BikeProvider1"),
BIKEPROVIDER2 ("BikeProbider2"),
private String name;
Provider(final String name) {
this.name = name;
}
}
public enum ServiceType {
BIKEINFORMATION ("BikeInformation"),
FINDBIKE ("FindBike");
private String type;
ServiceType(final String name) {
this.type = name;
}
}
*****************BikeProviderService interface**************************
// this is the interface that all providers will implement
public interface BikeProviderService {
Optional<Bike> getBikeInformation(BikeRequest bike);
Optional<Bike> findBike(BikeRequest bike);
}
*****************BikeServiceImpl**************************
public class BikeServiceImpl implements BikeService {
// I autowired providers
@Autowired
private BikeProvider1 bikeProvider1;
@Autowired
private BikeProvider2 bikeProvider2;
//Get the provider from the properties
@Value("${bike.provider.primary:}")
private String primaryProvider;
// This is the call from controller endpoint /api/bike/v1/info
@Override
public Bike findBikeInformation(BikeRequest bike, ServiceType serviceType) throws ServiceProviderException, BikeNotFoundException {
return executeBikeInformationWithProvider(primaryProvider, bike, serviceType);
}
private Bike executeBikeInformationWithProvider(String provider, BikeRequest bike, ServiceType serviceType) throws ServiceProviderException, BikeNotFoundException {
// find the providers based on the property value
Optional<BikeProviderService> bikeProviderService = providerImplementation(provider);
Optional<Bike> bikeResponse;
if (BikeProviderService.isPresent()) {
try {
bikeResponse = bikeProviderService.get().getBikeInformation(bike);
if (bikeResponse.isPresent()) {
LogCacheService.logRequest(bikeResponse, provider, serviceType.getType());
return bikeResponse.get();
}
} catch (Exception ex) {
throw new ServiceProviderException("message");
}
}
throw new BikeNotFoundException("Bike Not found");
}
// This is the call from controller endpoint /api/bike/v1/find
@Override
public Bike findBike(BikeRequest bike, ServiceType serviceType) throws ServiceProviderException, BikeNotFoundException {
return executeFindBikeWithProvider(primaryProvider, bike, serviceType) ;
}
private Bike executeFindBikeWithProvider(String provider, BikeRequest bike, ServiceType serviceType) throws ServiceProviderException, BikeNotFoundException {
// find the providers based on the property value
Optional<BikeProviderService> bikeProviderService = providerImplementation(provider);
Optional<Bike> bikeResponse;
if (bikeProviderService.isPresent()) {
try {
bikeResponse = bikeProviderService.get().findBike(bike);
if (bikeResponse.isPresent()) {
LogCacheService.logRequest(bike, provider, serviceType.getType());
return bikeResponse.get();
}
} catch (Exception ex) {
throw new ServiceProviderException("message");
}
}
throw new BikeNotFoundException("Bike Not found");
}
private Optional<BikeProviderService> providerImplementation(String bikeProvider) {
Provider provider = null;
try {
provider = Provider.valueOf(bikeProvider.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ex) {
log.error("Bike:: invalid Provider {}" , bikeProvider);
}
if (provider != null) {
switch (provider) {
case BIKEPROVIDER1:
return Optional.ofNullable(bikeProvider1);
case BIKEPROVIDER2:
return Optional.ofNullable(bikeProvider2);
default:
return Optional.empty();
}
}
return Optional.empty();
}
I am trying really hard not to duplicate code, but I afraid I am making this code overcomplicated and super hard to maintain. adding a new provider will be
1.- added to the enum provider class
2.- added to the switch statement
3.- added the service and @AutoWire the class
I tried the factory pattern but I don't want to create a new instance of the provider every time. and is close to the providerImplementation
method that I have.
Also, I created the execute/helper methods because as I mentioned before, I would like to do a fallback or recover method with the fallback.Provider
in the future. Example If findBike fails I can call the executeFindBikeWithProvider with the fallback. executeFindBikeWithProvider(**fallbackProvider**, bike, serviceType)
thanks Let me know if you need more details.
Aucun commentaire:
Enregistrer un commentaire