对于正在经历单体地狱的团队,有一些策略可以摆脱这种现状。
绞杀者应用程序(Strangler Application),可以逐步将单体架构转换为微服务架构。绞杀者应用程序是一个由微服务组成的新应用程序,通过将新功能作为服务,并逐步从单体应用程序中提取服务来实现。随着时间的推移,当绞杀者应用程序实现越来越多的功能时,它会缩小并最终消灭单体应用程序。
不要做“一步到位,推动重来”式的改造,“一步到位”方式是指,切图从零开始开发一个全新的基于微服务的应用程序(彻底替换遗留的单体应用)。
尽早并且频繁的体现出价值
逐步重构微服务架构的好处是,可以立即获得投资回报。可以先将应用程序中高价值的不分迁移到微服务架构。利用微服务尽早交付的特点,有助于获得业务团队对重构工作的支持。
尽可能少对单体做出修改
应该避免对单体进行大范围的修改。因为这样的修改耗时、昂贵且具有风险。不过既然要重构,就无法避免对单体应用的修改。但是也有一些策略,去规避风险,较少冲沟的工作量。
部署基础设施:这对你来说还为时过早
在重构前期,只进行最小的投资。唯一不可获取的是自动化测试的部署流水线。等到有一定的实际经验,后再进行大规模的投入。
将单体应用重构为微服务架构的若干策略
- 将新功能实现为服务
- 隔离表现层和后端
- 通过将功能提取到服务中来分解单体
将新功能实现为服务
如果发现自己已经陷入了困境,就不要在给自己继续挖坑了。将新功能实现为服务,降低了单体的生长速度,加速了新功能的开发,还能开速展示微服务架构的价值。
把新的服务与单体集成。需要API Gateway(将新功能的请求路由到新服务,其他请求路由到单体应用)和集成胶水代码(使服务与单体应用互相调用)。
何时把新功能实现为服务
理想情况下,新功能在新服务中实现。但是对于功能太小,或者新功能和单体中代码紧耦合的情况下,还是建议,在单体中实现新功能。
隔离表现层与后端
另一个策略是,表现层和业务逻辑和数据访问层分开。表现层和其他两层通常存在清晰的边界。业务层中的粗粒度的API,是天然的接缝,可以沿着这条接缝,把应用程序进行拆分。
这种策略的好处是:
- 可以彼此独立的开发部书和扩展这两个应用。
- 业务逻辑层的一组远程API,可以被稍后开发的微服务调用。
提取业务能力到服务中
提取到服务中的功能是对单体应用自上而下的一个“垂直切片”。该切片包含以下内容:
- 实现API断带你的入站适配器
- 领域逻辑
- 出站适配器
- 单体的数据库模式
再通过需要API Gateway将新功能的请求路由到新服务,其他请求路由到单体应用,和集成胶水代码使服务与单体应用互相调用。
提取一项实现对业务至关重要且不断发展的功能的服务是值得的。入股哦没有太多的好处,那么在提取服务方面投入精力是没有价值的。
在拆分的过程中,可能会遇到一下挑战:
- 拆解领域模型
- 重构数据库
拆解领域模型
第一个挑战就是跨越服务边界的对象引用,保留在单体中的类可能会引用已移动到服务的类。
解决方式就是根据DDD聚合进行思考。聚合使用主键而非对象引用作为互相引用。
更大的挑战是提取嵌入在具有其他职责的类中的功能。
重构数据库
拆分领域模型,不仅设计代码更改,业务涉及到数据库中持久化的数据。拆分实体的时候,就需要拆分相应的数据库表并将新表移动到服务中。
可以使用复制数据的方式以避免更为广泛的更改。在过渡期内保留原模式,并在用触发器在原模式和新模式之间同步。
确定提取何种服务,以及何时提取
需要仔细确定提取服务的顺序,以获得最大的收益。
一种策略是,冻结单体架构的开发,并按需提取服务。好处是逼迫你打破单体,弊端是没有规划,不能体现工作的价值。
另一种策略是,根据提取应用程序模块获得的预期收益,对应用程序的模块进行排名。
愿意如下:
- 加速开发。
- 解决性能、可扩展或可靠性问题。如果要提取的不分存在性能、可扩展或可靠性问题,那么提取就有价值。
- 允许提取其他一些服务。模块间存在依赖关系,提取顺序肯能影响提取的难易程度。
设计服务与单体的协作方式
服务很少独立工作,他需要与单体协作。
一个重要的问题是维护服务和单体之间的数据一致性。特别是拆分时,会破坏原有的ACID事务。
设计集成胶水
- 集成胶水代码,需要以API的形式,封装实现细节。
- 在选择同步交互方式,或者让使用者维护数据副本,以满足服务与单体间的查询需求。
- 在更新操作的时候,就需要使用Saga等方式,保证数据的一致性。
- 使用防腐层,让服务与单体间进行通信,防止传统的单体领域模型污染服务的领域模型,他是在不同领域模型之间进行转换的一层代码。
- 单体应用发布和订阅领域事件,也需要一定的改造。一种是在所有发生改变的代码位置,手动发布领域事件,但耗时费力,可能遗漏。另一种是在数据库级别发布领域事件,例如事务逻辑拖尾或者轮询,但通常表示数据库的更改,而不是表示业务实体的更改。对于单体的订阅,如果单体赢哟更不支持消息代理客户端,可以编写“帮助器”订阅事件,在更新单体的数据库。
处理身份验证和访问授权
基于微服务的应用程序使用令牌(JWT)来传递用户身份。而单体应用,使用内存中会话状态并使用本地线程对象传递用户身份。在重构时的挑战是,需要同时支持基于单体和基于JWT的安全机制。
一种解决方案是,在用户登录有,API Gateway同时设置JSESSIONID和USERINFO,JSESSIONID可以支持单体的认证方式;而在请求服务时,API Gateway将USERINFO转换为Authorization header供服务使用。