模块化与微服务

已有太多关于从单体应用迁移到微服务的故事。除了说起来更顺口,微服务似乎也很容易从拆分肢解单体应用后获得。但是这种方式真的适合你的组织吗?的确,维护一个混乱的单体应用有很多缺陷。但是还有别的可行替代方法,而且常常被忽略,这就是模块化应用开发。在这篇文章里,我们将会探讨,这个替代方法能做什么,它又是如何与构建微服务相关。

为模块化而微服务化

”使用微服务,我们团队最终能各自独立工作“ 或者”我们的单体应用太复杂了,拖慢了进度" 。这些陈述仅是开发团队走向微服务化的众多原由中几个。其实,还有像对伸缩性和恢复性要求。开发者们似乎集体追寻的是一种模块化系统设计开发的方法。软件开发中模块性可被归结为以下三个指导原则:

  • 强封装 : 组件内部隐藏实现细节,导致系统的不同组件之间低的耦合度,团队成员可以隔离地工作在系统中解耦的各个部分上。
  • 良好定义的接口: 你不能隐藏所有东西(否则,你的系统不能做任何有意义的事情).所以,不同组件间定义良好稳固的接口是必须的。组件可以被任何遵循接口规范的实现替换。
  • 明确清晰的依赖:模块化系统意味着不同组件要协同工作。最好要有一个好的方式表达验证它们之间的关系。

这些原则里许多可以使用微服务实现。一个微服务可以使用任何方式实现,只要它向其他服务暴露良好定义的接口(时常是通过rest api方式)。它的实现细节内部于服务本身,可以变动,不用诉诸于系统范围的协调。 微服务之间依赖开发时不明确,导致运行时服务编排失败。所以 ,最后一个模块化原则应用在微服务上要需要更多的精力。

如此,微服务实现了重要的模块化原则,导致切实的好处:

  • 团队可以独立工作,单自伸缩
  • 微服务通常代码小,而目标明确,减少复杂度
  • 服务可以内部重构,或者被替换,而没有整体的影响

这么看来,有什么劣势呢? 好吧,当你从一个单体(尽管少许肥胖)应用变成一个分布式微服务架构。这个过程会把庞大的运维复杂度摆在眼前。突然间,你需要持续部署不同的服务(可能被容器化)。新的担忧出现了:服务发现,分布式日志,分布式追踪等等。现在,你更倾向出现众多分布式计算的错误。版本化接口和配置管理变成主要关注问题了,这个列表还在继续增长。

最后发现微服务之间的联结,有如此的繁杂度,如同在把这些微服务业务逻辑合并后的单独应用中一样。到了这里,你不能在简单地把单体应用拆分。鉴于意大利面条式代码在单体代码基中的问题,然后使用网络边界分割这些代码,加剧了这些缠绕代码的恶化。

模块化替代

难道这意味着,我们要么被分配到凌乱的单体应用,要么淹没在微服务的疯狂复杂中?模块化可以使用其他方法完成。至关重要的是,我们要在开发阶段,划分代码的边界并强制边界隔离。创建好的代码组织结构的单体应用同样可以完成这一目标。当然,那意味着我们可以通过获取编程语言,开发工具的帮助,来强制模块性原则。

例如,在java中有好几个模块系统可以帮助组织应用,OSGi 是其中众所周知的一个,随着java 9 的发布,原生模块系统被添加到java 平台。模块变成语言和平台的一部分,java模块可以对其他模块的依赖,公开导出接口,同时对实现类完成强封装。你可以在我即将出版的书Java 9 Modularity 中了解更多关于使用java 9 模块开发的知识。

其它语言提供了相似的机制。如,JavaScript 在ES2015版引入了模块系统。在那之前,node.js 已经为JavaScript 后端提供了非标准的模块系统。然而,作为动态语言,JavaScript在支持接口(类型)约束,模块封装上稍弱。你可以使用Typescript获取强类型的优势。微软.NET框架有着Java一样的强类型,但是它没有一个直接等价Java即将到来有着强封装,显式依赖的模块系统。但是,好的模块化已然可以通过.Net core 中标准化的控制反转模式和创建逻辑相关的片段来实现。甚至,c++ 也在尝试在将来的版本中添加模块系统

有意识地努力使用你所在平台的模块化特性,可以达到先前使用微服务同样的模块化优势。模块系统越好,你就能在开发中获得越多的帮助。不同的团队可以工作在不同部分上,良好定义的接口作为团队之间唯一的接触点。部署时,模块聚集一起作为单一的部署单元。这种方式,你可以显著避免切换到微服务开发管理的复杂与开销。不过这意味着,你的不同模块不能使用不同的技术栈开发,但是,你的组织真的为那种方式做好准备了吗?

设计模块

设计良好的模块与微服务,要求同等设计上的严谨度。模块应该建模领域内某个单一的有限上下文。选择微服务是个架构上重要的决策,一旦出错,波及面比较大。模块化应用内部边界更易用改动。我们使用的编程语言的类型系统及其编译器,通常支持跨模块重构。而在微服务内重新划分边界,往往涉及大量的人际沟通,防止系统在运行时崩掉。老实来说,你有多少次在首次就能划分好组件边界,甚至第二回能搞定?

许多方面,静态语言的模块支持为构建良好接口提供了基础。在应对变化上,调用某个模块导出类型上暴露的方法,往往远比调用某个微服务上暴露的REST 端点更健壮。现在,REST+JSON 到处都是,但是缺乏编译器检查数据模型,并不能提供良好的类型化互操作。加上,遍历网络,序列化/反序列化,并不是零开销,整个情形变得令人沮丧。而且,很多编程语言的模块系统允许你表达对其他模块的依赖关系,当依赖关系被违反,模块系统会拒绝。微服务之间的依赖,只有在运行时才成形,才能验证,导致系统很难调试。

模块也是代码所有权很自然的基础单位,团队成员可以负责整个系统一到多个模块。唯一跟其他成员共享的是模块的公开接口。跟微服务相比,模块间有更少的隔离,毕竟所有东西还运行是在同一进程内。

没有理由,单体应用内的模块不能像好的微服务一样拥有自己的数据。模块化应用内的分享是通过良好定义的接口或者是模块间的消息,而非通过共享的数据存储。跟微服务很大的差异就是,所有分享发生在进程内部。最终一致性方面问题,也不能被低估。使用模块化,最终一致性是变成一个从容,策略上的选择。你可以只是逻辑上分隔开数据,仍然存储在同一数据库,跨域事务。对于微服务而言,没有选择,最终一致性已被限定,只能去适配它。

什么时候微服务是合适的?

那么,什么时候该转变到微服务?直到目前,我们都是使用模块化来控制复杂度。这方面,模块化和微服务都能做到。但是有不同的挑战,除了那些目前已经解决的。

当你的组织达到了google,netflix那种规模,拥抱微服务变得完全合理。你们有能力搭建自己的平台,工具集,工程师的数量到了不在能使用任何单体应用方法。但是大部分组织到不了这个规模,即便你认为你的组织将会变成下一个十亿美元独角兽,一开始编写模块化单体应用,也没什么伤害。

单独再建一个微服务,另一个合理的原由是, 如果不同的服务,天然更适合使用一套不同的技术栈。再次重申,你得有那个规模,能吸引不同人才,跨越这些不同的技术栈,让整个平台正常运转。

微服务使能单独部署系统的不同部分,某些东西在大部分模块化平台很难做到,甚至不可能。隔离的部署增加了系统的弹性和容错。而且,不同的微服务伸缩特征可能不同,不同的服务可以部署到能力匹配的硬件上。模块化的单体应用也能水平拓展,但是里面的所有模块是一起展开。这可能不是最好的方式,尽管实践上,可以带你走的很远。

结论

一如往常,最佳选择是找到一个中间立场。两种方法都有可取之处,哪一个更好,其实取决于环境,组织,应用本身。为什么不使用模块化作为开始?以后,你总是可以选择切换到微服务。不用费力如做外科手术般去清理的单体应用,你已经有了合理清晰的模块边界。它们甚至不是二选一的方案,在微服务内部可以使用模块化组织代码。问题变成了, 为什么微服务要包含个微字?

即使,你的应用不再是单体模块化的了, 服务也不必是小到可以易于维护了。当微服务的复杂度已经超过了通常你认为微服务该有的那个程度时,在服务内部应用模块化原则。存在模块化与微服务共存的情况。减少你系统里面服务的数量,能带来真正的成本节约。 如果你已经受益于模块化,确保自己不再有唯微服务是从的心态了。捣腾那些正在进行中的模块化特性,或者你喜欢的技术栈的框架。获取强制的模块化设计支持,而非依赖编码惯例,避免纠缠凌乱的代码。做谨慎的选择,是否要引入微服务复杂度的惩罚。有时,这变得必要,但是常常你能找到更好的出路。

转载于:https://my.oschina.net/evilunix/blog/1475636

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值