【发布时间】:2020-04-16 15:24:13
【问题描述】:
三年前,我作为开发人员参与了我的第一个微服务项目。我对微服务概念一无所知。该项目正在构建为 Spring Boot 微服务。一般来说,没有什么特别的,但所有项目都采用了颇具争议的基于客户端库的微服务之间的集成方式。我认为那些客户端库是用天真的方式制作的。我会尽量给出他们的主要想法。
项目中有三个模块:*-api、*-client和*-impl。 *-impl 是一个成熟的 REST 服务,*-client 是这个 REST 服务的客户端库。 *-impl 和 *-client 模块依赖于 *-api(它们将 *-api 作为 maven 依赖项导入)。 *-api 又包含 Java 接口,这些接口应该由 *-impl 模块中的 @RestController 类和实现此 REST 服务的客户端库功能的类(通过 RestTemplate 或 FeignClient)实现。此外,*-api 通常包含 DTO,Bean Validation 和 Swagger 注释可能会覆盖这些 DTO。在某些情况下,这些接口可能包含来自 Spring-MVC 的 @RequestMapping 注释。因此@RestController 和FeignClient 的实现同时继承了@RequestMapping。
*-api
@ApiModel
class DTO {
@NotNull
private String field;
// getters & setters
}
interface Api {
@RequestMapping("/api")
void method(DTO dto)
}
*-客户端
@FeignClient("api")
interface Client extends Api {
// void method(DTO) is inherited and implemented at runtime by Spring Cloud Feign
}
*-impl
@RestController
class ApiImpl implements Api {
void method(@Validated DTO dto) {
// implementation
}
}
不难猜测,如果其他微服务会拉取*-client 依赖项,它可能会在其类路径中获得不可预测的传递依赖项。微服务之间也出现了紧密耦合。
我决定花一些时间研究这个问题并发现一些概念。首先,我了解了诸如this one 或Sam Newman 著名的Building Microservices book(“客户端库”一章)之类的广泛意见。我也知道Consumer Driven Contracts 和他们的实现——Pact 和Spring Cloud Contract。我决定是否要使用 Spring Boot 微服务开始一个新项目,我会尽量不制作客户端库并仅通过 Consumer Driven Contracts 耦合微服务。因此我希望达到最小的耦合。
在那个项目之后,我参与了另一个项目,它的构建方式几乎与第一个关于客户端库的项目相同。我试图与一个团队分享我的研究,但没有得到任何反馈,所有团队都继续制作客户端库。几个月后我离开了项目。
最近我成为了我的第三个微服务项目的开发人员,其中也使用了 Spring Boot。而且我面临着与前两个项目一样的客户端库使用方式。在那里我也没有得到任何关于 Consumer Driven Contracts 使用的反馈。
我想知道社区的意见。您在项目中使用哪种方式?上面提到的客户端库方式合理吗?
附录 1.
@JRichardsz 的问题:
- 客户端是什么意思? REST API 的客户端是 API 所有者提供的一种 sdk,允许客户端以简单的方式使用它 而是 http 低级实现。
- 集成是什么意思?您需要测试集成吗?
- 我认为您的要求与如何在多个 api 之间组织源代码有关。对吗?
答案:
这里我只考虑 Spring/Spring Cloud。如果我使用 Spring Boot 构建一个微服务,并且我想与另一个(微)服务交互/集成(这就是我所说的“集成”),我可以使用 RestTemplate(它是一种客户端库,不是它?)。如果我要使用 Spring Boot + Spring Cloud 构建微服务,我可以使用 Spring Cloud OpenFeign 用于与另一个(微)服务的交互(或集成)。我认为Spring Cloud OpenFeign 也是一种客户端库,不是吗? 在我的一般问题中,我谈到了由我工作的团队创建的自定义客户端库。例如有两个项目:microserviceA 和 microserviceB。每个项目都包含三个 maven 模块:
*-api、*-client和*-impl。暗示*-clientmaven 模块包含*-apimaven 模块。*-apimaven 模块也用作*-implmaven 模块中的依赖项。当微服务A(microserviceA-implmaven 模块)想要与微服务B 交互时,它会导入microserviceB-clientmaven 模块。因此 microserviceA 和 microserviceB 是紧耦合的。我所说的集成是指微服务之间的交互。例如,microserviceA 与 microserviceB 交互/集成。
我的观点认为 microserviceA 和 microserviceB 不能有共同的源代码(通过客户端库)。这就是我问这些问题的原因:
您在项目中使用哪种方式?是上面提到的方式与 客户端库合理吗?
附录 2。
我会尽量详细并举例说明。
简介。
当我参与构建为微服务的项目时,他们使用相同的方式来实现微服务之间的交互,即“客户端库”。它们不是封装低级 http 交互、将 http 主体(等等)序列化/反序列化为 RestTemplate 或 FeighClient 的客户端库。它们是自定义客户端库,其唯一目的是与唯一的微服务进行交互(请求/响应)。例如,有一些microservice-b 提供了一些microservice-b-client.jar(它是一个自定义客户端库),microservice-a 应该使用这个jar 与microservice-b 进行交互。它与RPC 实现非常相似。
示例。
微服务-b 项目
microservice-b-api maven 模块
pom.xml:
<artifactId>microservice-b-api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
HelloController 接口:
@Api("Hello API")
@RequestMapping("/hello")
public interface HelloController {
@PostMapping
HelloResponse hello(@RequestBody HelloRequest request);
}
HelloRequest dto:
@Getter
@Setter
@ApiModel("request model")
public class HelloRequest {
@NotNull
@ApiModelProperty("name property")
private String name;
}
HelloResponse dto:
@Getter
@Setter
@ApiModel("response model")
public class HelloResponse {
@ApiModelProperty("greeting property")
private String greeting;
}
microservice-b-client maven 模块
pom.xml:
<artifactId>microservice-b-client</artifactId>
<dependencies>
<dependency>
<groupId>my.rinat</groupId>
<artifactId>microservice-b-api</artifactId>
<version>0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
HelloClient 接口:
@FeignClient(value = "hello", url = "http://localhost:8181")
public interface HelloClient extends HelloController {
}
microservice-b-impl maven 模块
pom.xml:
<artifactId>microservice-b-impl</artifactId>
<dependencies>
<dependency>
<groupId>my.rinat</groupId>
<artifactId>microservice-b-client</artifactId>
<version>0.0</version>
</dependency>
</dependencies>
微服务B类:
@EnableFeignClients
@EnableSwagger2
@SpringBootApplication
public class MicroserviceB {
public static void main(String[] args) {
SpringApplication.run(MicroserviceB.class, args);
}
}
HelloControllerImpl 类:
@RestController
public class HelloControllerImpl implements HelloController {
@Override
public HelloResponse hello(HelloRequest request) {
var hello = new HelloResponse();
hello.setGreeting("Hello " + request.getName());
return hello;
}
}
application.yml:
server:
port: 8181
微服务项目
pom.xml:
<artifactId>microservice-a</artifactId>
<dependencies>
<dependency>
<groupId>my.rinat</groupId>
<artifactId>microservice-b-client</artifactId>
<version>0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
微服务类:
@Slf4j
@EnableFeignClients(basePackageClasses = HelloClient.class)
@SpringBootApplication
public class MicroserviceA {
public static void main(String[] args) {
SpringApplication.run(MicroserviceA.class, args);
}
@Bean
CommandLineRunner hello(HelloClient client) {
return args -> {
var request = new HelloRequest();
request.setName("StackOverflow");
var response = client.hello(request);
log.info(response.getGreeting());
};
}
}
MicroserviceA 运行结果:
2020-01-02 10:06:20.623 INFO 22288 --- [ main] com.example.microservicea.MicroserviceA : Hello StackOverflow
问题。
我认为这种微服务之间的集成方式(通过自定义客户端库)是一种错误的方式。 首先,微服务变得紧密耦合。其次 - 客户端库带来了不良的依赖关系。 尽管有这些情况,我工作的团队还是使用了这种奇怪的方式来实现微服务之间的集成。 我想知道这种方式使微服务的集成合理(正确)吗?在微服务之间进行集成的最佳做法是什么?
附注在我看来,Spring Boot 微服务应该通过 Consumer Driven Contracts(Spring Cloud Contract 或 Pact)耦合,仅此而已。你认为这是正确的方式吗?
【问题讨论】:
-
你可能想看看这个问题:stackoverflow.com/questions/52033686/…
-
您的问题是否与 @OlgaMaciaszek 所说的使用库(服务、dto 等)或合同测试模块化 Spring Boot 代码(几个 api)有关?
-
@JRichardsz 是关于如何在 spring-boot 微服务中组织客户端库。我们真的需要使用它们吗?我认为我在问题中提到的示例确实为微服务带来了紧密耦合,但大多数项目确实使用它们。为什么?在微服务之间创建集成的最佳做法是什么?
-
#1 客户是什么意思? REST API 的客户端是 API 所有者提供的一种 sdk,允许客户端以简单的方式使用它,而不是 http 低级实现。 #2 集成是什么意思?测试集成是您需要的吗? #3 我认为您的要求与如何在多个 api 之间组织源代码有关。对吗?
-
@JRichardsz 我已经在“附录 1”部分回答了您的问题。感谢您的帮助。
标签: spring-boot microservices spring-cloud clean-architecture client-library