自行实现
How small engineering teams can build distributive systems
小型工程团队如何构建分布式系统
I actually really love monoliths. But I’m the sort of engineer that wants my tool box to be full of tools, even weird looking ones that only become useful in very specific circumstances. The advice I typically give people about maintaining technology is to embrace the inevitability of Conway’s Law and factor in team structure, composition and size into architectural decisions. Small teams tend to build monoliths because small teams tend to be monoliths.
我实际上真的很喜欢巨石。 但是我是那种工程师,我希望我的工具箱里装满工具,甚至是看起来很奇怪的工具,这些工具仅在非常特定的情况下才有用。 我通常向人们提供的有关维护技术的建议是将康威定律的必然性以及团队结构,组成和规模的因素纳入架构决策中。 小型团队倾向于建立整体,因为小型团队往往是整体。
Monoliths also offer slightly better performance than service based architectures. At least at first. It’s only once products reach economies of scale that the benefits of being able to build, deploy and scale different components of a system separately outweigh the increased latency of making a network call when you could use a library. Of course when it’s time to make that switch the organization has reached a level of growth where its primary focus is competing, competing and more competing. Breaking up the monolith while also rolling out new features is like remodeling your house while having a dinner party at the same time. For a small company, new features and new sales will almost always win out. Once a small company has become a mature company the task of breaking up the monolith has been spread out over multiple teams and increased in complexity.
Monoliths还提供了比基于服务的体系结构更好的性能。 至少起初。 只有在产品达到规模经济后,才能分别构建,部署和扩展系统的不同组件的好处,才能超过使用库时进行网络调用的延迟时间。 当然,当该做出改变时,组织已经达到了增长水平,其主要重点是竞争,竞争和更多竞争。 打破整体结构,同时推出新功能,就像在同时举行晚宴的同时重新装修房屋。 对于一家小公司而言,新功能和新销售几乎总能取胜。 一旦一家小型公司成为一家成熟的公司,打破整体的任务就分散在多个团队中,并且复杂性也随之增加。
At Rebellion we had another reason to sometimes favor more monolithic approaches. Government networks can be complex and unfriendly to cloud. That can mean operating on a private, possibly “air gapped”, network with edge devices where we can reproduce a cloud like experience without connecting to a traditional cloud environment. An architecture that can be coupled or decoupled easily can offer advantages.
在Rebellion,我们还有另一个理由有时倾向于采用更单一的方法。 政府网络可能很复杂并且对云不友好。 这可能意味着要在具有边缘设备的专用网络(可能存在“空隙”)上运行,在此我们可以在不连接传统云环境的情况下重现类似云的体验。 可以轻松耦合或解耦的体系结构可以提供优势。
All Rebellion products are built on top of a framework called servicecore. At the heart of servicecore is a gRPC server, then built into servicecore are many of the tools engineers need to build good applications anyway. When engineers use servicecore they get monitoring for free, they get tracing for free, they get proper logging and health checks for free, ORM integrations and easier migrations too. They also get things that can be enabled as needed but are turned off by default, like an interface for Rebellion customers to manage access control roles in a self- service fashion. But each one of these options is itself built using the servicecore framework, meaning they can be run as separate services or combined into a tightly coupled monolithic application with no real code changes.
所有Rebellion产品都建立在称为servicecore的框架之上。 servicecore的核心是gRPC服务器,然后,构建到servicecore中的是工程师无论如何都需要构建良好应用程序的许多工具。 当工程师使用servicecore时,他们可以免费获得监视,他们可以免费获得跟踪,他们可以免费获得适当的日志记录和运行状况检查,ORM集成以及更轻松的迁移。 他们还获得了可以根据需要启用但默认情况下已关闭的功能,例如供Rebellion客户以自助服务方式管理访问控制角色的界面。 但是,每个选项本身都是使用servicecore框架构建的,这意味着它们可以作为单独的服务运行,也可以组合成紧密耦合的整体应用程序,而无需更改实际代码。
砖头和螺柱:类似于Legos的代码 (Bricks and Studs: Code like Legos)
Much has been written about designing applications in order to facilitate microservices. For example, domain orientated architecture organizes like functionality together so that larger services can eventually be split up. But most advice in this space doesn’t help you reconfigure code easily. It’s hard to break services off and even harder to pull them back together if for some reason the division ended up being a mistake. You have to design solutions around places where functions can logically connect in order to have that level of flexibility.
关于设计应用程序以促进微服务的文章很多。 例如,面向领域的体系结构将类似的功能组织在一起,以便最终可以拆分更大的服务。 但是,此领域的大多数建议并不能帮助您轻松地重新配置代码。 如果由于某种原因最终导致部门划分错误,那么很难中断服务,甚至更难以将它们重新组合在一起。 您必须在功能可以逻辑连接的地方设计解决方案,以实现这种灵活性。
When building a monolith that can break itself up the first question is how should two sets of functionality be coupled? The most basic way is for the code to exist in the same place, and then when the application has scaled to the size where it makes sense to maintain two different services the code must be moved into separate repositories and rebuilt to be run independently from one another.
在构建可以分解的整体时,第一个问题是如何将两组功能结合在一起? 最基本的方法是将代码存在于同一位置,然后在应用程序扩展到可以维护两个不同服务的大小时,必须将代码移到单独的存储库中并重新构建以独立于一个代码运行另一个。
Another solution is for one service to be implemented as a package and imported by a host service. This keeps the code for each domain separate and means multiple services can reuse the same parts, but often requires the host service to build out endpoints and interfaces that interact with the package logic. As soon as we need a database, or a frontend, importing a package isn’t so convenient.
另一种解决方案是将一项服务实现为包,并由主机服务导入。 这将每个域的代码分开,这意味着多个服务可以重用相同的部分,但是通常需要宿主服务来构建与包逻辑交互的端点和接口。 一旦我们需要数据库或前端,导入包就不那么方便了。
But by building the framework that allows those elements to either be run on their own server, or injected into another server, we can couple and uncouple sets of functions as needed.
但是,通过构建允许这些元素在自己的服务器上运行或注入到其他服务器中的框架,我们可以根据需要耦合和解耦功能集。
At the heart of servicecore is an application object (well… this being Go and all it is in fact a struct, but object is a more familiar term to most programmers). An engineer looking to build a service on top of servicecore need only specify the configuration they want in the application object and pass that into servicecore. Servicecore iterates through all the application objects, gathers and sorts the configuration, creates the gRPC server and passes the full configuration on to that server.
Servicecore的核心是一个应用程序对象(嗯……这就是Go,实际上它只是一个结构,但是对象是大多数程序员更熟悉的术语)。 希望在servicecore之上构建服务的工程师只需在应用程序对象中指定所需的配置,然后将其传递给servicecore。 Servicecore遍历所有应用程序对象,收集和排序配置,创建gRPC服务器,并将完整配置传递到该服务器。
For even the most basic service, there are multiple application objects, because the tooling that makes servicecore valuable to developers are all themselves application objects as well. Therefore, servicecore makes it possible to couple services together in two different ways: either we can use the application object to attach the endpoints and all their logic and dependencies to the same server, effectively extending it. Or the application objects can inject middleware to change the functionality of the server’s endpoints.
即使是最基本的服务,也存在多个应用程序对象,因为使servicecore对开发人员有价值的工具本身也是应用程序对象。 因此,servicecore使得可以通过两种不同的方式将服务耦合在一起:我们可以使用应用程序对象将端点及其所有逻辑和依赖项附加到同一服务器上,从而有效地扩展它。 或者,应用程序对象可以注入中间件来更改服务器端点的功能。
中间件重新流行 (Middleware Is Back in Style)
Admittedly, I cut my teeth as a software engineer during a period where middleware was the villain of architectural patterns. Middleware was “software glue” that in the wrong hands could change the behavior of a function in a way that was hidden from the software developer debugging it. But modern day web services have a lot of overlapping needs. Behaviors like logging and authentication have to be executed for every single request, every single time. Who wants to throw a good tool out of the toolbox just because other engineers might misuse it? I remember working with frameworks where to avoid the middleware curse the architect wanted you to use boilerplate code calling those functions in a transparent way at the top of each endpoint. Leave off the line that checks if the user is logged in? Guess that page is now public.
诚然,在中间件是体系结构模式的反面人物的那段时间里,我作为软件工程师不遗余力。 中间件是“软件胶水”,用不正确的手可以改变功能的行为,而这种方式对于调试它的软件开发人员来说是隐藏的。 但是现代Web服务有很多重叠的需求。 必须针对每个单个请求,每次执行诸如日志记录和身份验证之类的行为。 谁会因为其他工程师可能滥用它而将好的工具丢出工具箱? 我记得在使用避免中间件诅咒的框架时,架构师希望您使用样板代码以透明的方式在每个端点的顶部调用这些函数。 不用检查用户是否已登录? 猜猜该页面现已公开。
The opportunities to add middleware fit into two categories. They either execute before the function (pre-hooks) or after the function (post-hooks). We can determine some of the options a framework like servicecore could expose by tracing the path of a typical request through the application. No matter what the particular endpoint is supposed to do, a request moves through the following stages:
添加中间件的机会分为两类。 它们要么在函数之前(预钩)执行,要么在函数之后(后钩)执行。 我们可以通过跟踪应用程序中典型请求的路径来确定诸如servicecore之类的框架可以提供的某些选项。 无论特定端点应该做什么,请求都将经历以下阶段:
Every endpoint must receive a request, confirm that what is sending the request has permission to do so, execute the function tied to the endpoint (potentially triggering queries to other services or databases) and deliver a response.
每个端点都必须接收一个请求,确认发送请求的内容具有这样做的权限,执行与该端点绑定的功能(可能触发对其他服务或数据库的查询)并传递响应。
Since we’re using gRPC we have a built in way of hooking into the transmission and response stages via interceptors. Even if we’re streaming data, we can attach logic that is executed before the request data is passed to the actual endpoint. This turns out to be a good way to implement that authorization phase. It can also be used to add logging for easy debugging.
由于我们使用的是gRPC,因此我们有一种内置的方式,可以通过拦截器连接到传输和响应阶段。 即使我们正在传输数据,我们也可以附加在将请求数据传递到实际端点之前执行的逻辑。 事实证明,这是实施授权阶段的好方法。 它还可以用于添加日志记录,以便于调试。
Then there are hooks related to the lifecycle of the service itself. We definitely want to allow configuration to be passed between the host service and the extension. We’re using Cobra to do command line interfaces for our services, so servicecore can include hooks to add commands either when Cobra is initialized or at runtime for the server.
然后是与服务本身的生命周期相关的挂钩。 我们绝对希望允许在主机服务和扩展之间传递配置。 我们正在使用Cobra来为我们的服务创建命令行界面,因此servicecore可以包含钩子,以在Cobra初始化时或在服务器运行时添加命令。
These connection points give us a lot of flexibility to add functionality to products without scaling up the overhead on the engineering team by asking them to both maintain the code and an ever growing fleet of servers in clusters per service. The complexity of distributed systems is ultimately valuable, but only once the usage levels on the products themselves have scaled up enough to justify it. Before then running services decoupled is just another set of responsibilities added to the work load of a small team.
这些连接点使我们能够灵活地向产品添加功能,而又不要求工程人员维护代码以及每个服务群集中服务器群的不断增长,而不会增加工程团队的开销。 分布式系统的复杂性最终是很有价值的,但是只有在产品本身的使用水平已经扩大到足以证明其合理性时,它才有价值。 在此之前,分离服务的运行只是添加到小型团队工作负载中的另一组职责。
伟大的力量和重大的责任 (Great Power and Great Responsibility)
One of the things that servicecore does is enforce conventions. By creating a framework that packages up configuration and creates a gRPC server for you, we can add validation logic that ensures how things are named and some aspects of how they behave are consistent.
servicecore要做的一件事是强制执行约定。 通过创建打包配置并为您创建gRPC服务器的框架,我们可以添加验证逻辑,以确保事物的命名方式以及行为方式的某些方面是一致的。
But the other side of building a framework like this is that it could be used to produce bloated services that are difficult to understand and debug. One big issue is the order in which applications are loaded into servicecore. Duplicates are ignored and hooks are executed in order. Changing that order might impact outcomes in ways not obvious to all engineers. The potential problems of middleware have not gone away.
但是,建立这样一个框架的另一方面是,它可以用于产生难以理解和调试的肿服务。 一个大问题是应用程序加载到Servicecore中的顺序。 重复项将被忽略,并且挂钩将按顺序执行。 更改顺序可能会以对所有工程师都不明显的方式影响结果。 中间件的潜在问题尚未消除。
Servicecore is called servicecore because it’s meant to serve as the core of services, not full products, but it’s also easier to maintain products when you’re able to give ownership of a set of functions to a 1 or 2 person team and not burden them with separate monitoring, on-call rotations, and deploys.
Servicecore之所以称为Servicecore,是因为它是服务的核心,而不是完整的产品,但是当您能够将一组功能的所有权分配给一个或两个人的团队而又不会给他们造成负担时,维护产品也更加容易具有单独的监控,呼叫轮换和部署。
We’re a small team now, but we’re growing fast. Check out our open positions.
我们现在是一个小团队,但是我们发展很快。 查看我们的空缺职位 。
翻译自: https://medium.com/rebellion-defense/the-monolith-that-breaks-up-itself-c9513c732367
自行实现