真正的 Nexus PIM 服务模板:共享主干、清晰边界、更快交付
在之前的文章中,我们梳理出了三个清晰的限界上下文:attribute(商品属性)、taxonomy(商品分类)和 catalog(商品目录)。现在,我们来回答一个更实际的问题:当我们着手创建这些新服务时,如何才能让这些精心划分的限界边界保持可复用,而不是昙花一现?
这不是一个停留在理论阶段的讨论,我们会带你直接走一遍仓库里真实存在的项目骨架,看看一个标准的微服务是如何从零开始“长”出来的。
为什么统一的骨架至关重要
梳理出符合 DDD 思想的限界边界已经颇具挑战。然而,梳理完成后,新的挑战随之而来:如果每个微服务都采用不同的初始化方式、依赖版本、代码结构,那么团队的维护成本和日常的运维复杂度将会迅速飙升,导致架构在无形中发生“漂移”。
为了避免这种情况,在 Nexus PIM 项目中,我们决定采用一个统一的 Spring Boot 3 项目骨架。这个骨架为所有服务提供了共同的基础,确保了从诞生的那一刻起,它们就遵循着相同的设计语言和规范。
多模块的独立入口
每个限界上下文都对应一个独立的 Spring Boot 应用入口。以商品目录服务为例,它的入口类 CatalogApplication 结构非常清晰:
@SpringBootApplication
class CatalogApplication
fun main(args: Array<String>) {
runApplication<CatalogApplication>(*args)
}
AttributeApplication 和 TaxonomyApplication 也保持了完全相同的结构。这种一致性让开发、构建和部署流程变得极其简单且可预测。
包结构:统一的分层规范
三个服务都采用了严格相同的顶层包结构,这是团队内部的“共同语言”:
service
|-- application // 应用层:编排业务流程
|-- domain // 领域层:核心业务逻辑与模型
|-- infrastructure// 基础设施层:外部依赖实现
`-- web // Web层:接口暴露
这种统一性极大地降低了开发人员的上下文切换成本。工程师从一个服务切换到另一个服务时,无需重新适应项目结构,可以立即投入业务逻辑的开发,将精力聚焦在真正的业务差异上,而不是框架的差异性上。
运行时配置:服务独立,集成可控
为了实现真正的服务自治,每个服务在运行时都使用独立的端口和数据库:
catalog:端口 8081,数据库 nexus_catalog
attribute:端口 8082,数据库 nexus_attribute
taxonomy:端口 8083,数据库 nexus_taxonomy
当服务间需要协作时(例如 catalog 需要调用 attribute 和 taxonomy),我们通过配置显式声明集成地址,而不是隐式耦合:
integration:
attribute:
base-url: http://localhost:8082
taxonomy:
base-url: http://localhost:8083
这套配置规则清晰地传达了一个核心架构原则:服务间不共享数据库,只通过 API 进行集成。这为未来的独立部署、扩缩容和技术演进打下了坚实基础。
Web层模板:薄控制器与统一的错误约定
Web 层的设计遵循“薄控制器”理念。以 catalog 服务的商品控制器为例:
@RestController
@RequestMapping(“/api/catalog/products”)
class ProductController(
private val productService: ProductService
) {
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun createProduct(@Valid @RequestBody req: CreateProductRequest) =
productService.createProduct(...).toResponse()
}
控制器仅负责 HTTP 协议相关的任务,如参数绑定、状态码返回和简单的 DTO 转换,所有业务逻辑都被下沉到应用服务层。
错误处理同样被标准化,通过统一的 @RestControllerAdvice 确保所有服务的 API 行为一致且可预测:
@RestControllerAdvice
class ApiExceptionHandler {
@ExceptionHandler(NoSuchElementException::class)
fun notFound(ex: NoSuchElementException) = ...// 返回404
@ExceptionHandler(IllegalArgumentException::class)
fun badRequest(ex: IllegalArgumentException) = ...// 返回400
}
运维基础:探活接口与Actuator
所有服务都暴露了轻量级的存活探测接口,便于健康检查。以 attribute 服务为例:
@RestController
class PingController {
@GetMapping(“/api/attribute/ping”)
fun ping(): Map<String, String> =
mapOf(“service” to “attribute”, “status” to “ok”)
}
同时,骨架默认开启了 Spring Boot Actuator 的 health 和 info 端点,方便与现有的监控报警系统(如 Prometheus, Grafana)进行集成,为服务可观测性提供了开箱即用的支持。
持久化初始选型
在骨架层面,我们已经固定了基础的持久化技术栈,避免了每个服务在选择上的分歧:
- Spring Data JPA(用于数据访问抽象)
- PostgreSQL(作为关系型数据库)
- Flyway(用于数据库版本迁移)
每个服务在初始化时就包含了基础的数据库迁移脚本(例如 V1__init.sql)。所有对数据库 Schema 的变更都通过 Flyway 脚本进行,并提交到 Git 仓库,使得每一次 Schema 变更都有据可查、可追溯。
当前实现中的服务间通信
在当前阶段,不同限界上下文之间的通信采用 REST 客户端模式。调用关系可以简单表示为:
[Catalog 商品目录] ----HTTP----> [Taxonomy 商品分类]
|+----HTTP---------> [Attribute 商品属性]
[Taxonomy 商品分类] ---HTTP----> [Attribute 商品属性]
具体的客户端实现都被放置在各自服务的基础设施层,例如:
catalog.infrastructure.client.TaxonomyServiceClient
catalog.infrastructure.client.AttributeServiceClient
taxonomy.infrastructure.client.AttributeServiceClient
这个设计强制执行了一条关键的架构边界规则:服务之间只能通过已发布的服务契约(API)进行通信,严禁直接连接其他服务的数据库。这保障了服务的封装性和独立性。
测试基础:上下文冒烟测试
每个服务都自带一个极简但极其有用的启动测试(Smoke Test):
@SpringBootTest
class ApplicationSmokeTest {
@Test
fun contextLoads() {
// 如果 Spring 应用上下文启动失败,这个测试会直接失败
}
}
这个测试虽然小,但对于持续集成(CI)管线的稳定性帮助巨大。它能在代码合并后的第一时间发现因配置错误、依赖缺失或环境问题导致的启动失败,避免将有问题的构建产物推送到后续环节。
当前已实现 vs 后续计划
本文重点介绍的是已经在代码仓库中落地实现的核心骨架部分,它主要包括:
- 统一的 Gradle 多项目基础配置
- 标准的四层包结构(Web, Application, Domain, Infrastructure)
- 每个服务独享数据库和端口的运行时模型
- 基于 REST 的集成客户端模式
至于更高级的架构组件,如完整的事件驱动总线、CQRS 查询模型、高级搜索投影等,这些属于项目长期路线图中的内容。它们建立在当前这个坚实的骨架之上,我们会在后续的文章中逐一展开探讨。
总结
在微服务架构中,单独创建一个 Spring Boot 应用或许很简单,但创建并长期维护一堆始终保持一致性、可维护性和可运维性的微服务,却是一项复杂的系统工程。
在我们的实践中,这种一致性并非凭空而来,它源于一个精心设计且被严格遵守的可复用项目骨架。这个骨架定义了:
- 相同的构建模板与依赖管理
- 相同的包结构与分层语言
- 相同的 Web、错误处理和测试基础规范
- 清晰的物理边界规则(数据库独享 + API 集成)
这种标准化的力量,让团队能够将宝贵的时间和精力从重复的框架搭建和配置工作中解放出来,真正聚焦于领域业务逻辑的创新与实现。如果你也在探索微服务的最佳实践,不妨在 云栈社区 与更多开发者交流,共同打磨属于你们团队的高效开发骨架。
原文链接:https://medium.com/@ContentsofthePizza/building-a-pim-system-part-4-a-kotlin-spring-boot-3-microservice-skeleton-08c67ca5ca3a