同时为单体和微服务设计java项目
Posted
技术标签:
【中文标题】同时为单体和微服务设计java项目【英文标题】:Designing java project for monoliths and microservices at same time 【发布时间】:2018-05-11 16:45:21 【问题描述】:我想知道您如何在 java 中将项目模块划分为单体应用程序,以便以后将模块转换为微服务? 我的个人命名如下所示:
com.company.shopapp.product
...product.domain (ddd, services, repositories, entities, aggregates, command handlers - everything with package scope)
...product.api (everything with public scope)
...product.controller (CQRS endpoints for commands in web perspective - (package scope))
...product.query(CQRS - package scope)
com.company.shopapp.sales
- domain
- api
- controller
- query
我们在这里拥有的基本上是作为包的产品管理上下文和销售上下文。
模块之间仅使用公共接口(api 包)进行通信。在我的项目中,我使用“..api.ProductFacade”来集中通信点。
当我的“销售”模块增长时,我将通过将“..api.ProductFacade”接口实现为“rest”或“soap”客户端将其转变为微服务,另一方面,我将基于 ProductFacade 创建 Endpoint/RestController界面。 包“com.company.shopapp.product.api”将转换为扩展库并添加到两个项目中。
编辑: 我可以使用@Feign 库开箱即用地实现这一目标。 https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#spring-cloud-feign-inheritance
整个想法感觉不错,但也许您有更好的方法来设计项目并确保将其分解为微服务不会破坏整个应用程序。
【问题讨论】:
关于“太宽泛”的轻快。 “固执己见”......但是,嘿 - 非常有趣的问题! 也许这个有用:medium.com/@wrong.about/… 【参考方案1】:我认为你的模块结构很好。但我建议您创建一个真正的“多模块”项目 (link)。这样,使用来自另一个模块的代码会产生一个编译错误。这将帮助您保持良好的意愿!
为此,您必须将每个模块拆分为 private(实现)和 public(api,仅接口)模块(通过这样做,您不需要“api”包)。 实现模块可以依赖于任何公共模块,但不能依赖私有模块。
如果您将应用程序连接到私有模块中,使用依赖注入,私有模块将没有“内部”依赖! 私有模块没有任何“compile-time”依赖,只有“runtime”依赖。
这里是快速的模块依赖图:
我希望你觉得这很有用!
编辑: 您只需要一个额外的模块来引导应用程序!
【讨论】:
【参考方案2】:微服务由功能和连接程度组成。 我使用了这种方法:
com.company.product: 可能的大服务: 配置 服务 域 等 可能的第二大服务: 配置 服务 域 等 配置 服务 // 可能永远不会分开 domain //公共域 等当拆分项目时,您可以通过包分析新的公共依赖,排除公共库,为每个微服务复制项目,删除不必要的代码,也许更改服务实现(例如,如果“可能的大服务”使用“可能的第二大服务” )、配置和构建。 在这种情况下,“大”是指某事物的完整功能实现,可以水平扩展,或者出于其他原因需要成为微服务。
【讨论】:
【参考方案3】:TLDR:分别考虑组件和模块并建立它们的“接触点”
在您的示例中,模块看起来像横切结构,与推荐的微服务实践非常吻合。因此,它们都可以成为单个微服务的一部分。如果您要使用 DDD,则需要在包路径中包含有界上下文名称。
在我自己的源代码中,我通常分隔(在顶层)模块,例如 config
(用于加载和解析,嗯,配置),functional
用于功能核心,域模型,operational
用于管理并发, Akka actor 结构、监控等,以及adapters
,所有 API、DB 和 MQ 代码都存放在其中。最后,模块app
,所有模块都在此启动,接口绑定到实现。此外,您通常有一些utils
或commons
用于较低级别的样板、算法等。
在一些建筑学校中,模块和组件之间存在明确的分离。前者是源代码结构的一部分,后者是运行时单元,它们消耗资源并以特定的方式存在。
在您的情况下,微服务对应于此类组件。这些组件可以在同一个 JVM 中运行——你得到一个整体。或者它们可以在(可能)单独的主机上的单独 JVM 中运行。然后你称它们为微服务。
所以,你需要:
使每个组件的源代码自主,以便它可以在单独的运行时空间(如类加载器、线程、线程池、actor 系统子树)中启动。因此,您需要一些启动器才能将其变为现实。然后您就可以通过您的public static void main(...)
调用它。
在您的代码中引入一些模块,这些模块将分别保存单个组件的语义。以便您从代码中了解组件的作用域。
抽象组件之间的通信,以便您可以使用适配器(源代码模块)通过网络进行通信,或使用过程调用或 Akka 的消息传递等 JVM 内部机制。
我应该注意到,在较低级别上,您可以在组件中使用公共源代码模块,因此它们可以在代码中有一些交集。但是在更高级别的源代码会很独特,因此您可以根据组件将其拆分为模块。
您可以使用 Akka 并在监督子树中运行每个组件,其中子树的监督者是组件的主要参与者。那么主要参与者定义将是您组件的主要模块。如果需要让组件进行通信,则应将相应的 ActorRefs 作为配置参数传递给适配器。
您谈到集中通信点,但在我看来,如果您坚持微服务范式和组件的高度自治,那么对于每个 API 调用,都必须有人拥有合约。输入不同的 DDD 有界上下文交互模式。如果你把它放在某个集中管理的模块中,每个组件都应该使用它,那么这就是 API 治理的一个例子。只要您是唯一的维护者,这可能会很方便。但当不同的开发者(甚至团队)各司其职时,您需要考虑新的条件再次做出此决定。
稍后您将组件拆开 - 然后您会将 URL 传递给适配器而不是 ActorRefs。
【讨论】:
非常好的答案!以上是关于同时为单体和微服务设计java项目的主要内容,如果未能解决你的问题,请参考以下文章
单体的 TienChin 和微服务的 TienChin 有何异同?
单体的 TienChin 和微服务的 TienChin 有何异同?