Mar*_*unt 5 client-library spring-boot microservices spring-cloud clean-architecture
三年前,我作为开发人员参与了我的第一个微服务项目。我对微服务概念一无所知。该项目正在构建为 Spring Boot 微服务。一般来说,没有什么特别的,但所有项目都应用了基于客户端库的微服务之间的集成方式,这种方式颇有争议。我认为那些客户端库是通过幼稚的方式制作的。我会尽量给出他们的主要想法。
项目中有三个模块:*-api,*-client和*-impl. 这*-impl是一个成熟的 REST 服务,并且*-client是此 REST 服务的客户端库。*-impl和*-client模块依赖于*-api(它们*-api作为 maven 依赖项导入)。在*-api又包含哪些应该由执行Java接口@RestController从类*-impl模块,并通过实现客户端库的功能,这REST服务(通过类RestTemplate或FeignClient)。另外,*-api通常包含可被覆盖的DTO Bean验证和扬鞭注释。在某些情况下,这些接口可能包含来自 Spring-MVC 的@RequestMapping注释。因此@RestController和FeignClient同时的实现继承了@RequestMapping。
*-api
@ApiModel
class DTO {
@NotNull
private String field;
// getters & setters
}
interface Api {
@RequestMapping("/api")
void method(DTO dto)
}
Run Code Online (Sandbox Code Playgroud)
*-客户
@FeignClient("api")
interface Client extends Api {
// void method(DTO) is inherited and implemented at runtime by Spring Cloud Feign
}
Run Code Online (Sandbox Code Playgroud)
*-impl
@RestController
class ApiImpl implements Api {
void method(@Validated DTO dto) {
// implementation
}
}
Run Code Online (Sandbox Code Playgroud)
不难猜测是否有其他微服务会拉取*-client依赖,它可能会在它们的类路径中获得不可预测的传递依赖。微服务之间也出现了紧密耦合。
我决定花一些时间研究这个问题并发现一些概念。首先我的结识了广泛的意见,像这一个或山姆·纽曼的著名建筑物微服务书(章“客户端库”)。我还了解了消费者驱动的合同及其实现 - Pact和Spring Cloud Contract。我决定是否使用 Spring Boot 微服务开始一个新项目,我将尽量不制作客户端库和Consumer Driven Contracts仅通过微服务耦合。因此我希望达到最小的耦合。
在那个项目之后,我参与了另一个项目,它的构建方式与关于客户端库的第一个项目几乎相同。我试图与一个团队分享我的研究,但我没有得到任何反馈,所有团队都继续制作客户端库。几个月后我离开了项目。
最近,我成为了我的第三个微服务项目的开发人员,其中也使用了 Spring Boot。而且我面临着客户端库的使用方式与前两个项目相同。在那里我也无法获得有关Consumer Driven Contracts使用的任何反馈。
我想知道社区的意见。你在你的项目中使用哪种方式?上面提到的客户端库方式是否合理?
@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 模块。还*-api用作 maven 模块中的依赖项的*-implmaven 模块。当 microserviceA(microserviceA-implmaven 模块)想要与 microserviceB 交互时,它会导入microserviceB-clientmaven 模块。因此 microserviceA 和 microserviceB 是紧密耦合的。
我所说的集成是指微服务之间的交互。例如,微服务 A 与微服务 B 交互/集成。
我的观点总结为 microserviceA 和 microserviceB 不得具有公共源代码(通过客户端库)。这就是我问这些问题的原因:
你在你的项目中使用哪种方式?上面提到的客户端库方式是否合理?
我将尝试详细解释并举例说明。
当我参与构建为微服务的项目时,他们使用相同的方式来实现微服务之间的交互,即“客户端库”。它们不是其incapsulate低电平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>
Run Code Online (Sandbox Code Playgroud)
HelloController 接口:
@Api("Hello API")
@RequestMapping("/hello")
public interface HelloController {
@PostMapping
HelloResponse hello(@RequestBody HelloRequest request);
}
Run Code Online (Sandbox Code Playgroud)
HelloRequest dto:
@Getter
@Setter
@ApiModel("request model")
public class HelloRequest {
@NotNull
@ApiModelProperty("name property")
private String name;
}
Run Code Online (Sandbox Code Playgroud)
HelloResponse dto:
@Getter
@Setter
@ApiModel("response model")
public class HelloResponse {
@ApiModelProperty("greeting property")
private String greeting;
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
HelloClient 接口:
@FeignClient(value = "hello", url = "http://localhost:8181")
public interface HelloClient extends HelloController {
}
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
微服务B类:
@EnableFeignClients
@EnableSwagger2
@SpringBootApplication
public class MicroserviceB {
public static void main(String[] args) {
SpringApplication.run(MicroserviceB.class, args);
}
}
Run Code Online (Sandbox Code Playgroud)
HelloControllerImpl 类:
@RestController
public class HelloControllerImpl implements HelloController {
@Override
public HelloResponse hello(HelloRequest request) {
var hello = new HelloResponse();
hello.setGreeting("Hello " + request.getName());
return hello;
}
}
Run Code Online (Sandbox Code Playgroud)
应用程序.yml:
server:
port: 8181
Run Code Online (Sandbox Code Playgroud)
微服务-一个项目
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>
Run Code Online (Sandbox Code Playgroud)
微服务A类:
@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());
};
}
}
Run Code Online (Sandbox Code Playgroud)
MicroserviceA 运行结果:
2020-01-02 10:06:20.623 INFO 22288 --- [ main] com.example.microservicea.MicroserviceA : Hello StackOverflow
Run Code Online (Sandbox Code Playgroud)
我认为微服务之间的这种集成方式(通过自定义客户端库)是一种错误的方式。首先,微服务变得紧密耦合。第二 - 客户端库带来了不受欢迎的依赖。尽管有这些情况,我工作的团队使用这种奇怪的方式在微服务之间进行集成。我想知道这种方式是否可以使微服务的集成合理(正确)?在微服务之间进行集成的最佳实践是什么?
PS 在我看来,Spring Boot 微服务应该通过消费者驱动的契约(Spring Cloud Contract或Pact)来耦合,而不是其他任何东西。您认为如何正确?
免责声明:我认为您的问题没有一个明确的答案。更具体地说,我相信最好的解决方案会随着项目的发展而变化,因此以下内容在很大程度上可以被视为“个人意见”。我希望人们能像我一样表达他们的意见,而不是对我的答案进行投票,无论是赞成还是反对。
微服务的目的之一确实是“简化”软件产品的集成和演进,这实际上引发了将客户端锁定到通用 API 库的好处的问题。
然而,由于甚至有公司从微服务迁移回单体应用的故事,我永远不敢说一种方法绝对是错误的或绝对是正确的。
在某些情况下,使用客户端库可能不是一个坏主意,因为额外的负担可能会迫使不守纪律的开发人员进行协调并保证更新的客户端始终与实际服务一起开发。尽管如此,除非我有特定的需求,否则它不会是我的第一选择,可能更多地与公司内不同开发团队使用的技能水平和方法的差异有关。
我个人认为最简单的方法(customer contracts)非常适合具有少量客户端/客户(另一个微服务是客户端/客户)的应用程序,并且可以立即高效工作,这有助于缩短上市/发布时间,同时支持公司的初创阶段。
随着公司的发展,由于维护成本的增加和相关的挫败感,对更多结构的需求开始出现,并且需要审查选择,customer contracts此时有关业务和相关需求的可用信息极大地有助于选择“下一个”方式去,这可能是customer-driven contracts因为它们是封闭的和完整的,出于多种原因,这是可取的,而且只有在了解对客户重要的事情之后才能实现。
痛苦的经历可能会导致选择依赖客户端库,但我相信这种情况并不常见,而且更有可能发生在“全新”项目上,因为技术领导留下了来自先前项目的“未处理的创伤”,该项目超出了customer contracts模式它应该迁移到customer-driven contracts。
对我来说,关键是在一开始就做出一个关键选择,为未来几乎完全改变想法的可能性腾出空间,从而支持未来的增长,而不必立即“拔掉”旧客户的插头,因为业务连续性并不重要不允许这样的举动。一种方法是为 API 提供一个包含在 URL 中的代号,从而允许将来的版本整齐地分开,从而为消费者提供升级的宽限期。代号实际上是您公司销售的产品的名称。
要明确地尝试回答您的问题:
您在项目中使用哪种方式?这实际上取决于谁将使用我的微服务。如果它是我公司内的特定参与者并且也使用 Java,那么当我向他们公开微服务时,我更愿意提供一个成熟的客户端(带有相关的源代码),并要求他们在向他们公开微服务时也这样做。我。这至少可以避免一些问题,比如他们责备我,因为“它不起作用”,而问题出在他们的客户端,也可以防止对给定微服务的内部工作原理的误解,并推动参与者开发微服务相当稳定(也就是说:人们会避免不断更改“签名”,以避免必须重建相应的客户端......基本上,我利用了我们天生的懒惰)。显然他们不需要使用我的客户端,我也不需要使用他们的客户端:这更多的是微服务工作的“声明”,旨在让各方通过检查彼此的客户端代码来找出真正的问题所在。这还允许查看它们的编码方式,从而深入了解微服务代码的预期质量,从而了解微服务代码的稳健性和可预测性。
上述客户端库的方式合理吗?这种方式让微服务集成合理(正确)吗?有时是,有时不是。老实说,我不会使用客户端库,但对于简单的架构来说,它可以工作,并且可以确保每个人都在同一页面上,所以这不一定是坏事。
微服务之间集成的最佳实践是什么?我相信它会随着时间的推移、根据项目的不同而发生变化。我会开始让消费者自己照顾自己,以支持更快的上市时间,我很清楚我必须从consumer contracts(尽管我会尝试在某种程度上使架构面向未来)开始,并让经验和成长得到巩固将架构合二为一consumer-driven contracts。
你觉得怎样才是正确的做法?正确的方法是让您能够先于竞争对手将产品推向市场,但又不会妨碍未来的增长。老实说,这并不是一个很好的答案,但事实是你的问题很难回答,并且很大程度上取决于项目的范围。关键是,正确的方法很可能会随着时间的推移而改变,因此您应该致力于选择一种您认为能够实现 3-5 年增长的解决方案,同时提供一种应急方案,使您能够优雅地迁移到将支撑未来8-10年的增长。这也意味着“正确的方式”不仅是一个技术问题,也是一种业务管理方法,特别是一种可以对未来进行系统规划的方法。
| 归档时间: |
|
| 查看次数: |
2181 次 |
| 最近记录: |