简体   繁体   English

用于具有多个提供程序的服务的Spring Boot设计(小型服务)

[英]Spring Boot design for service with multiple providers(small services)

I am creating a Spring boot application and I need help with the design of my services. 我正在创建一个Spring Boot应用程序,我在设计服务时需要帮助。 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. 端点将调用Bike服务,该服务可能会调用不同的提供程序,例如BikeProvider1,BikeProvider2等。提供程序将在应用程序的属性中指定,这允许更改提供程序而无需重新启动应用程序。

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. 我还认为,将来我希望有2个提供商作为后备,以防第一个提供商没有响应。 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. 在BikeServiceImpl.java中,我动态找到所需的提供程序并进行调用。

*******************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 right now: 立即添加新的提供程序:

1.- add to the enum provider class
2.- add to the switch statement
3.- add 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. 并且接近于我拥有的providerImplementation方法。

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. 另外,我创建了执行/辅助方法,因为正如我前面提到的,我愿做一回退或与恢复方法fallback.Provider的未来。 Example If findBike fails I can call the executeFindBikeWithProvider with the fallback. 示例如果findBike失败,我可以调用带有后备功能的executeFindBikeWithProvider。 executeFindBikeWithProvider(**fallbackProvider**, bike, serviceType)

thanks Let me know if you need more details. 谢谢,让我知道您是否需要更多详细信息。

Isn't this a good use case for Spring @Profile? 这不是Spring @Profile的好用例吗?

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html

https://www.baeldung.com/spring-profiles https://www.baeldung.com/spring-profiles

Instead of using the list of providers in your properties file, you could change your runtime configuration using different profiles. 您可以使用其他配置文件来更改运行时配置,而不是使用属性文件中的提供程序列表。 This will effectively allow you to have your providers be ignored or enabled based on your profile setting. 这将有效地使您根据个人资料设置忽略或启用您的提供程序。

This way, adding a new provider will only require you implementing the new provider and associating it with a profile. 这样,添加新的提供程序仅需要您实现新的提供程序并将其与配置文件相关联。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM