三年前,我作为开发人员参与了我的第一个微服务项目。我对微服务概念一无所知。该项目正在构建为 Spring Boot 微服务。一般来说,没有什么特别的,但所有项目都采用了颇具争议的基于客户端库的微服务之间的集成方式。我认为那些客户端库是用天真的方式制作的。我会尝试给出他们的主要想法。
项目中有三个模块*-api
:*-client
和*-impl
。这*-impl
是一个成熟的 REST 服务,并且*-client
是这个 REST 服务的客户端库。*-impl
和*-client
模块依赖于(它们作为 Maven 依赖项*-api
导入)。*-api
反过来包含 Java 接口,这些*-api
接口应该由模块中的@RestController类*-impl
和实现此 REST 服务的客户端库功能的类(通过RestTemplate或FeignClient)实现。通常还包含Bean Validation和Swagger*-api
可能涵盖的 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
依赖项,它可能会在其类路径中获得不可预测的传递依赖项。微服务之间也出现了紧密耦合。
我决定花一些时间研究这个问题并发现一些概念。首先,我从 Sam Newman 著名的《构建微服务》一书(“客户端库”一章)中了解了像这样的广泛观点。此外,我还了解了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
. 暗示*-client
maven 模块包含*-api
maven 模块。maven 模块也*-api
用作 maven 模块中的依赖*-impl
项。当 microserviceA(microserviceA-impl
maven 模块)想要与 microserviceB 交互时,它将导入microserviceB-client
maven 模块。因此 microserviceA 和 microserviceB 是紧密耦合的。我所说的集成是指微服务之间的交互。例如,microserviceA 与 microserviceB 交互/集成。
我的观点认为 microserviceA 和 microserviceB 不能有共同的源代码(通过客户端库)。这就是我问这些问题的原因:
您在项目中使用哪种方式?上述使用客户端库的方式是否合理?
附录 2。
我将尝试详细解释并举例说明。
介绍。
当我参与构建为微服务的项目时,他们使用相同的方式来实现微服务之间的交互,即“客户端库”。它们不是封装低级 http 交互、将 http 主体(等等)序列化/反序列化为 or 的客户端RestTemplate
库FeighClient
。它们是自定义客户端库,其唯一目的是与唯一的微服务进行交互(请求/响应)。例如,有一些microservice-b
提供一些microservice-b-client.jar
(它是一个自定义客户端库)并且microservice-a
应该使用它jar
来与microservice-b
. 它与RPC实现非常相似。
例子。
微服务-b项目
微服务-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;
}
}
应用程序.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
问题。
我认为这种微服务之间的集成方式(通过自定义客户端库)是一种错误的方式。首先,微服务变得紧密耦合。其次 - 客户端库带来了不良的依赖关系。尽管有这些情况,我工作的团队还是使用了这种奇怪的方式来实现微服务之间的集成。我想知道这种方式是否可以使微服务的集成合理(正确)?在微服务之间进行集成的最佳实践是什么?
PS 在我看来,Spring Boot 微服务应该通过Consumer Driven Contracts(Spring Cloud Contract或Pact)耦合,仅此而已。你觉得怎么走是对的?