How To Design Software Architecture For Startups
如何为初创企业设计软件架构
A good software architecture for a startup looks very different from the one you might build for an enterprise system. Here is what worked for us.
初创公司的良好软件架构与企业系统的架构大不相同。以下是我们成功的经验。
Table of contents 目录
- What does our app do?
我们的应用程序有什么功能? - Microservices usually don't work well for startups
微服务通常对初创公司不太适用 - Move fast and outsource things
快速行动并外包事务 - Consider building reusable things
考虑构建可重复使用的物品 - Be pragmatic 务实一些
- Boundaries along sync/async communication patterns
同步与异步通信模式之间的界限 - How we did it and how we would do it next time
我们是如何做到的,以及下次我们会如何改进 - About flexibility 关于灵活性
There is a lot of information out there on how to build software for enterprise systems. If you are designing a system for a startup, a lot of these patterns and techniques simply don't work well. High levels of uncertainty, the need for maximum flexibility and a quick pace as well as serious restrictions in (wo-)manpower are challenges that are often unique to startups. On the other hand, startups can make compromises that enterprises can't make.
关于如何为企业系统构建软件的信息有很多。然而,如果你正在为初创公司设计系统,许多这些模式和技术并不适用。初创公司通常面临独特的挑战,如高度的不确定性、对最大灵活性的需求、快速的节奏以及人力资源的严重限制。另一方面,初创公司可以做出一些企业无法做出的妥协。
Deciding on a good backend architecture for our app, häpps, was one of the most challenging decisions we had to make. Even for experienced software architects, this decision is always a complicated one because it sets the stage for future development. The fact that we are a startup and therefore only two programmers with no extra capital complicates everything further. Also, making architecture decisions is an ongoing process, because young apps tend to evolve in unforeseen ways. It's essential to have a backend architecture that can quickly evolve and adapt to new requirements. Flexibility is a crucial requirement - it may even be the most important one. But what does it mean to build a flexible architecture for a startup?
为我们的应用程序 häpps 选择一个合适的后端架构是我们面临的最具挑战性的决策之一。即使对于经验丰富的软件架构师来说,这个决定也总是复杂的,因为它为未来的开发奠定了基础。作为一家初创公司,我们只有两名程序员,且没有额外的资金,这使得问题更加复杂。此外,架构决策是一个持续的过程,因为年轻的应用程序往往会以不可预见的方式发展。因此,拥有一个能够快速适应新需求的后端架构至关重要。灵活性是一个关键要求,甚至可能是最重要的要求。那么,为初创公司构建一个灵活的架构究竟意味着什么呢?
Throughout my career, I have designed (or was involved in the design of) many backend architectures for both startups and enterprise systems. All of that experience went into the design and re-design of our own app backend. In this blog post, I'd like to share our discussions, arguments and insecurities, the resulting architecture, and the challenges and compromises we faced. I'll present the architecture that worked for us and what I would change if I had to do it all over again. However, please note that this article is not a definitive manual that is suitable for all apps. It is also quite opinionated, and the approaches presented may be debatable.
在我的职业生涯中,我曾为初创企业和企业系统设计(或参与设计)了许多后端架构。这些经验都被应用到了我们自己的应用后端的设计和重新设计中。在这篇博客文章中,我想分享我们的讨论、争论和疑虑,以及最终确定的架构,还有我们面临的挑战和妥协。我将介绍对我们有效的架构,以及如果从头再来,我会做出哪些改变。不过,请注意,本文并非适用于所有应用的权威指南,内容也较为主观,所提出的方法可能会有争议。
PermalinkWhat does our app do?
我们的应用程序有什么功能?
Very simplified, the app allows the user to create so-called happenings. These are temporary groups organized not around a fixed set of people but around an occasion, something like a gathering or a party. Users can invite people to happenings and invited users can accept or decline invitations. Once a user accepts an invitation, they are part of that happening and can read and write in a chat and participate in further planning. Every happening also has a gif, video or image as its background media. On top of that, users have profiles and can befriend each other. A surprisingly complex part of the backend system is only concerned with push notifications. These notifications are sent on a great number of occasions: From friend requests to new chat messages, reactions, accepted invitations, time or place changes or as a reminder for open invitations and upcoming happenings.
简而言之,这款应用允许用户创建所谓的“活动”。这些活动是围绕某个场合(如聚会或派对)组织的临时小组,而不是固定的人群。用户可以邀请他人参加活动,被邀请者可以选择接受或拒绝邀请。一旦用户接受了邀请,他们就可以成为该活动的一部分,参与聊天、阅读和发送消息,并参与进一步的计划。每个活动还配有一个 GIF、视频或图片作为背景。此外,用户拥有个人资料,并且可以互相加为好友。后端系统中一个复杂的部分专门负责推送通知。 这些通知会在多种情况下发送,包括好友请求、新聊天消息、点赞、接受的邀请、时间或地点的更改,以及未处理邀请和即将发生的事件的提醒。
The app as it is now has a fairly complex backend architecture spanning at least a dozen services and multiple means to share data and events between them. All of this was built mostly by one person and all of it runs smoothly. The overall level of complexity at the time of writing is much bigger than it was in the beginning. But mainly because of small features and improvements that required a lot of back-and-forth of information between services. The app's main features mentioned above are pretty much the ones we built from the very beginning.
当前的应用程序拥有一个相当复杂的后端架构,涉及至少十几个服务以及多种数据共享和事件传递的方式。这些大部分是由一个人构建的,并且运行得非常顺畅。与最初相比,目前的整体复杂性要高得多,这主要是由于需要在各个服务之间频繁传递信息的小功能和改进。上面提到的应用程序的主要功能,基本上是我们从一开始就构建的。
PermalinkMicroservices usually don't work well for startups
微服务通常对初创公司不太适用
When we started building häpps, the microservice hype was full on and deeply influenced our discussions. We quickly agreed that the term microservice is somewhat misleading because it seems to suggest cutting services as small as possible. Splitting your codebase into multiple independent services comes with a lot of disadvantages: Each service has to be maintained and deployed and increases the boilerplate needed for shared data structures, complicated communication protocols and infrastructure. By the time we released our app, the common library used by most services has been updated over a hundred times. And every service has to follow suit eventually.
当我们开始开发 häpps 时,微服务的概念正风靡一时,并深刻影响了我们的讨论。我们很快意识到,“微服务”这个术语有些误导性,因为它似乎暗示着要将服务拆解得尽可能小。然而,将代码库分割成多个独立服务会带来许多弊端:每个服务都需要单独维护和部署,并且会增加共享数据结构、复杂通信协议和基础设施所需的样板代码。到我们发布应用时,大多数服务使用的公共库已经更新了上百次,而每个服务最终都必须随之更新。
And there is more: Most advantages of multi-service architectures for some enterprise-sized systems won't be relevant for a startup at all.
此外,对于某些企业级系统而言,多服务架构的许多优势对初创公司来说并不适用。
The most important one: The ability to deploy services independently doesn't matter for early startups. You don't have autonomous teams if you only have one or two teams. In this scenario, the individual deployment of multiple services is an impediment, not an advantage. The concern of the whole system crashing because one developer messed up on one end is also irrelevant as long as everyone works on all ends at once and you don't risk revenue losses because hundreds of users use your platform at the same time.
最重要的一点:对于早期初创公司来说,独立部署服务的能力并不重要。如果你只有一两个团队,就不存在自治团队的概念。在这种情况下,多个服务的单独部署反而是一种障碍,而不是优势。只要每个人都在同时处理所有方面的工作,并且你不会因为数百名用户同时使用平台而面临收入损失的风险,整个系统因为某个开发人员在某一端出错而崩溃的担忧也就无关紧要了。
If you are lucky enough to reach a point where the horizontal scaling of a monolith isn't enough to keep your backend going, you will also be at a point where you have to rewrite big parts of your business logic and make performance improvements in data structures, database queries, caching and serialization. If you start with a relatively small user base, using up-to-date technology and frameworks, you should easily be able to scale your systems two or three orders of magnitude before you have to change your macro architecture.
如果你足够幸运,达到了单体的水平扩展无法满足后端需求的地步,那么你也将面临必须重写大部分业务逻辑,并在数据结构、数据库查询、缓存和序列化等方面进行性能优化的挑战。如果你从一个相对较小的用户基础开始,并采用最新的技术和框架,你应该能够轻松地将系统扩展两到三个数量级,之后才需要考虑调整宏观架构。
So all of this sounds like a strong case against a multi-service architecture - and it is. But as you will see, we somewhat came around eventually. There are still reasons you might split up your backend. And there are better and worse ways to do it. If making services as small as possible in their distinct domain isn't the solution, then how do we determine reasonable boundaries?
因此,所有这些似乎都强烈反对多服务架构——确实如此。但正如你将看到的,我们最终还是有所转变。仍然有一些理由可能会促使你拆分后端。而且,拆分的方式有好有坏。如果尽可能将服务在其特定领域中做得尽可能小并不是解决方案,那么我们该如何确定合理的边界呢?
PermalinkMove fast and outsource things
快速行动并外包事务
There are a lot of components in your app backend that – although crucial – don’t relate to the core domain of your app. These are also the services that you shouldn’t build yourself:
你的应用后端中有许多组件——尽管非常重要——但与应用的核心领域无关。这些服务也是你不应该自己构建的:
Auth services, obviously
认证服务,显而易见A push notification or cloud message distributor
推送通知或云消息分发器Media services that store uploaded images and take care of things like downsizing, cropping, scaling and caching
存储上传图像并负责缩小、裁剪、缩放和缓存等功能的媒体服务A chat server (?)
一个聊天服务器(?)
One of my favorite decisions on this project was to use Google Firebase. We knew we didn’t want to use it for data storage or computation (and we’re glad we did not!). But we implemented Firebase Auth so we could use their simple kits to take care of everything auth-related, including social logins and password resets. These are the kind of things that you, as an app developer that has to deliver features, can’t afford to spend more time on than necessary to ensure safety.
在这个项目中,我最满意的决定之一是使用 Google Firebase。我们明确不打算用它来存储数据或进行计算(事实证明这是个明智的选择!)。但我们采用了 Firebase Auth,利用其简洁的工具包来处理所有与认证相关的事务,包括社交登录和密码重置。作为应用程序开发者,在必须交付功能的情况下,这些事务不应花费过多时间来确保安全性。
We also used Google Firebase Cloud Messaging to have a single interface that delivers push notifications to both Android and IOS devices. This decision served us well for a while but Google's lack of flexibility to cover the ever-growing complexity of our push notifications made us doubt the decision. We might have been better off with a system like OneSignal.
我们还使用了 Google Firebase Cloud Messaging,以便通过一个统一的接口向 Android 和 IOS 设备发送推送通知。这个决定在一段时间内效果不错,但随着推送通知的复杂性不断增加,Google 的灵活性不足让我们开始质疑这个选择。或许使用像 OneSignal 这样的系统会更好。
We ended up using a self-developed service to take care of our uploaded media for the only reason that we had it sitting around from an old project with similar requirements. If you don’t already have one, I would recommend using a CDN or some kind of ready-deployable open-source container to do the job. Media management gets quite complicated quite fast when you try to keep your user's data usage within reasonable boundaries.
我们最终选择使用一个自研的服务来处理上传的媒体,唯一的原因是我们之前在一个有类似需求的旧项目中已经开发了这个服务。如果你还没有类似的解决方案,我建议使用 CDN 或某种可立即部署的开源容器来完成这项工作。当你试图将用户的数据使用量控制在合理范围内时,媒体管理很快就会变得非常复杂。
The chat server is somewhat of an unclear case. While building a chat is a rather complex problem, it is also one that has also been solved hundreds of times before. We had lengthy discussions about whether we should implement a ready chat server solution or build our own on top of a more low-level WebSocket framework. We eventually decided to do the latter and I am glad we did. All in all, it turned out that the actual chatting part of the chat server was easier to implement than we thought (that’s only true for the backend!). On the other hand, the chat turned out to be tightly coupled with many of the other app's core features, so it would have become more and more difficult to build on a generic product.
聊天服务器的情况有些复杂。虽然构建一个聊天系统是一个相当复杂的问题,但这个问题已经被解决过无数次。我们进行了长时间的讨论,决定是采用现成的聊天服务器解决方案,还是在更底层的 WebSocket 框架上自行构建。最终我们选择了后者,并且我很高兴我们做出了这个决定。总的来说,聊天服务器的实际聊天部分比我们预想的要容易实现(这仅适用于后端)。另一方面,聊天功能与应用程序的许多其他核心功能紧密耦合,因此如果使用通用产品,构建起来会变得越来越困难。
While we're at it, let's talk about reusable components for backend systems.
既然我们谈到了这个话题,不妨讨论一下后端系统的可重用组件。
PermalinkConsider building reusable things
考虑构建可重复使用的物品
To think about reusability on a level that goes beyond one app might seem a little over the top - but I promise you, it's not. You might be convinced that what you build now will be a hit, but the more likely scenario is that you fail, learn from it and end up building something again. But even if that's not the case - if the product you're building will be a success, building somewhat reusable things will increase your ability to adapt to whatever user feedback you get for your MVP.
考虑在一个超越单个应用的层面上思考可重用性,可能看起来有些过头——但我向你保证,这并非如此。你可能会认为你现在构建的东西会大受欢迎,但更有可能的情况是你失败了,从中吸取教训,并最终再次构建一些东西。但即使不是这样——如果你正在构建的产品会成功,构建一些可重用的东西将增强你适应 MVP 用户反馈的能力。
By the time we started to build our second app, it was obvious that we wanted to move as fast as possible to be able to validate our ideas. We decided to use familiar technologies and a simpler architecture. We also looked at old projects to find reusable code. Our media service - a simple python app that handled image uploads and served static media - was one component that could be reused with minor modifications.
当我们开始开发第二个应用时,很明显我们希望尽快推进,以便验证我们的想法。我们决定采用熟悉的技术和更简单的架构。我们还回顾了旧项目,寻找可重用的代码。我们的媒体服务——一个处理图片上传并提供静态媒体的简单 Python 应用——是一个只需稍作修改即可重用的组件。
We knew that this time might not be the last time we decide to build a product - whether it would be a success or not. So we decided to try to build reusable services and components.
我们知道这次可能不是我们最后一次决定开发产品——无论成功与否。因此,我们决定尝试构建可重用的服务和组件。
Not every service can or should be built with reusability in mind. One of the mistakes I witness the most is a tendency to build something generic for something that is very specific: Your domain logic. The result of this is an overly complicated, boilerplate code base that slows down your product development. Thinking about this will also push you in the right direction. Which part of the system does represent the core business logic of your product and which doesn't? The backbone of your chat server? It shouldn't have any special knowledge about your app. The user profile service? Doesn't care what kind of profiles it manages. The push notification gateway? It only manages device tokens and forwards notifications to Firebase. The async message hub? Its job is to register clients and deliver arbitrary JSON messages. All of these systems can be built in a reusable manner. They should be! That doesn't necessarily mean they all have to be standalone services connected by HTTP APIs. If you commit to a programming language and a web framework, you might as well build them as modules and keep them isolated well enough to be able to separate them from your monolith at any time in the future.
并非所有服务都能或应该以可重用性为目标进行构建。我见过最常见的错误之一是为非常特定的领域逻辑构建过于通用的东西。这样做的结果是代码库变得过于复杂和模板化,从而拖慢了产品开发速度。思考这一点也能帮助您找到正确的方向。系统的哪一部分代表了产品的核心业务逻辑,哪一部分不是?聊天服务器的核心部分?它不应该对您的应用程序有任何特殊了解。用户资料服务?它不关心管理的是哪种资料。推送通知网关?它只负责管理设备令牌并将通知转发到 Firebase。异步消息中心呢? 它的主要功能是注册客户端并传递任意的 JSON 消息。所有这些系统都可以设计为可重用的形式。它们确实应该如此!这并不意味着它们都必须是通过 HTTP API 连接的独立服务。如果你选择了一种编程语言和一个 Web 框架,你也可以将它们构建为模块,并保持足够的隔离性,以便在未来的任何时候都能轻松地将它们从单体应用中分离出来。
There should be no pressure to make everything as reusable as possible. After all, your goal is still to deliver quickly and in the end, it's not certain if you'll ever have another use case for these systems. But thinking about reusability also forces you to think about your whole backend system in terms of domain logic and isolation, something that will pay off later on.
不必强求将所有内容都做到尽可能可重用。毕竟,你的首要目标仍然是快速交付,而且最终是否会有其他用例使用这些系统也不确定。然而,考虑可重用性也会促使你从领域逻辑和隔离的角度来思考整个后端系统,这将为未来带来回报。
PermalinkBe pragmatic 务实一些
Because not everything that works on paper is fun to implement in practice. We ended up splitting some systems and joining others based on compromises and pure pragmatism.
因为并不是所有在理论上可行的东西在实践中都很有趣去实现。我们最终根据妥协和纯粹的实用性拆分了一些系统,并合并了其他系统。
Our friendship service is one example of this. The app enables users to add others as friends. So in its essence, the corresponding service only manages relations between user ids. In theory, it could be used for all kinds of apps, so in the beginning, it was a separate service. But we soon realized that it was tightly coupled to the user profile service: Often, if a list of user profiles is returned to a client, each profile in the list should include a flag indicating if that user is a friend of the user requesting the list. On the other hand, when a list of friends is returned from the friendship service, it should often include profile information such as the users' names. We eventually ended up joining these services into one, but keep their logic in separate modules.
我们的好友服务就是一个例子。该应用允许用户将其他人添加为好友。因此,从本质上讲,该服务仅管理用户 ID 之间的关系。理论上,它可以用于各种应用,因此在最初它是一个独立的服务。但我们很快发现它与用户资料服务紧密相关:通常,当向客户端返回用户资料列表时,列表中的每个资料都应包含一个标志,表明该用户是否是请求列表的用户的好友。另一方面,当从好友服务返回好友列表时,通常需要包含用户的姓名等资料信息。最终,我们将这些服务合并为一个,但将它们的逻辑保留在单独的模块中。
It's easy to lose yourself in thought loops about isolated systems. Consider an API that should return a list of friend suggestions. These suggestions might include friends of friends - information that's stored in the friendships service. But in a second iteration, you may also want to suggest people who attended the same happening as the user. This information comes from a completely different system and it would be easy to argue, the friendship suggestions should actually be a microservice of its own since it combines data from different domains. Then again, the minimal amount of business logic required to join lists of user profiles to a single list of suggestions doesn't justify the maintenance and deployment of a service of its own. In a situation like this, it might instead just be time to look the other way and implement some domain awareness between otherwise independent systems. It's not pretty but it works: Have one of your existing services return the user's friendship suggestions, even if it's not a perfect fit. In our case, we decided to let the happenings service do the job, to keep the friendships service clean of code that belongs to another domain. You might as well choose to do it the other way around or build a kind of miscellaneous service that works as a collection of smaller services.
很容易陷入关于孤立系统的思维循环中。假设有一个 API 需要返回朋友推荐列表。这些推荐可能包括朋友的朋友——这些信息存储在友谊服务中。但在第二次迭代中,你可能还想推荐那些与用户参加过同一活动的人。这些信息来自一个完全不同的系统,因此很容易认为,朋友推荐实际上应该是一个独立的微服务,因为它结合了来自不同领域的数据。然而,将用户资料列表合并为单一推荐列表所需的最少业务逻辑并不足以证明维护和部署一个独立服务的合理性。 在这种情况下,或许应该换个思路,在原本独立的系统之间引入一些领域感知。虽然这并不完美,但确实有效:可以让你现有的某个服务返回用户的友谊建议,即使这些建议并不完全匹配。在我们的案例中,我们决定让事件服务来处理这项工作,以保持友谊服务的代码简洁,避免混杂其他领域的逻辑。你也可以选择相反的方式,或者构建一个杂项服务,作为多个小型服务的集合。
PermalinkBoundaries along sync/async communication patterns
同步与异步通信模式之间的界限
Another reason to split your system into services relates to scalability - kind of. Consider a complex action that triggers a lot of complicated, asynchronous side effects.
将系统拆分为服务的另一个原因与可扩展性有关——某种程度上。想象一个复杂的操作,它会引发许多复杂的、异步的副作用。
Take for example a user who decides to join a happening. On the happening service level, a new relation between the user and the happening will be created before the backend sends an asynchronous update prompt to all registered clients, asking them to reload the updated happening from the server. This is a fast, synchronous request. But this action triggers a lot of other side-effects: A system message will be sent to the chat backend so that an info message is displayed in the chat - but only if that feature is activated. Personalized push notifications will be sent to an unknown number of friends and other relevant happening members. That in turn requires querying the database for other relations between happenings and users, existing friendships, user profiles and notification settings. The system will also have to update the notification badge count on the IOS devices of everyone who can see the happening. Determining the correct badge count, however, requires recounting or gathering the overall number of unread chat messages and several other pieces of information.
例如,一个用户决定参加某个活动。在活动服务层面,用户和活动之间的新关系将在后端向所有注册客户端发送异步更新提示之前创建,要求他们从服务器重新加载更新后的活动。这是一个快速的同步请求。然而,这一操作会引发许多其他连锁反应:系统消息将被发送到聊天后端,以便在聊天中显示一条信息消息——但前提是该功能已启用。个性化的推送通知将被发送给未知数量的朋友和其他相关活动成员。这反过来需要查询数据库中活动与用户之间的其他关系、现有的友谊、用户个人资料和通知设置。 系统还需要更新所有能够查看该事件的 IOS 设备上的通知徽章计数。不过,要确定正确的徽章计数,需要重新统计或汇总未读聊天消息的总数以及其他相关信息。
Performing multiple tasks synchronously can result in long request times, ultimately leading to failed requests. Additionally, the number of side effects generated by these tasks tends to increase at a faster rate than the number of core features. Even if you wrote every line of code in your project yourself, it can be challenging to determine how many other actions are triggered by complex actions.
同步执行多个任务可能会导致请求时间过长,最终导致请求失败。此外,这些任务产生的副作用数量往往比核心功能数量增长得更快。即使你亲自编写了项目中的每一行代码,也很难确定复杂操作会触发多少其他操作。
In the bigger picture, decoupling these things is usually a smart decision. Splitting a system along these boundaries e.g. by using a message broker like rabbitMQ or Kafka can have advantages in terms of scalability later on. However, in the early stages of product development, it's often sufficient to just stick to one app and make use of internal event emitters.
从整体来看,解耦这些部分通常是一个明智的选择。通过使用像 rabbitMQ 或 Kafka 这样的消息代理来拆分系统,可以在未来的扩展性方面带来优势。不过,在产品开发的初期阶段,通常只需专注于一个应用程序并利用内部事件发射器就足够了。
PermalinkHow we did it and how we would do it next time
我们是如何做到的,以及下次我们会如何改进
We ultimately divided our backend system into multiple services, based on all the considerations discussed above. Secondary services for authentication, push notifications, media management and monitoring were a no-brainer.
我们最终根据上述所有考虑将后端系统划分为多个服务。身份验证、推送通知、媒体管理和监控等辅助服务是显而易见的。
We also decided to separate systems along communication patterns and reusability considerations. However, we soon learned a lesson in pragmatism: Systems that talk a lot to each other - and only synchronously - should not be separated at first, even if their domains suggest it. Instead, strict modularization will take you a long way.
我们还决定根据通信模式和可重用性来分离系统。然而,我们很快学到了实用主义的一课:那些彼此频繁通信且仅同步通信的系统,即使它们的领域建议分离,最初也不应分开。相反,严格的模块化将为你带来长远的益处。
We are still incredibly pleased with the architecture we selected. It runs smoothly and scales effortlessly, particularly when we add new features. Even though it involves multiple domain services communicating with each other, we've encountered almost no issues related to inter-service communication. This is largely thanks to our comprehensive end-to-end test coverage and our straightforward yet robust communication patterns between services.
我们对我们选择的架构依然非常满意。它运行流畅,扩展自如,尤其是在添加新功能时。尽管涉及多个领域服务之间的通信,但我们几乎没有遇到与服务间通信相关的问题。这主要得益于我们全面的端到端测试覆盖以及简洁而强大的服务间通信模式。
But this also required a lot of work: We spent a fair amount of time developing a common library that did not contain any domain logic but all the interfaces and data structures required for the communication between services. This again, was a compromise in favor of keeping things easy.
但这同样需要大量的工作:我们花费了大量时间开发一个公共库,这个库不包含任何领域逻辑,但包含了服务之间通信所需的所有接口和数据结构。这再次是为了保持简单而做出的妥协。
Developing robust inter-service communication protocols that are suitable for a startup takes time as well. All of this was good work that we would have to do sooner or later if our app would have been a smashing success - which it wasn't. And that's kind of the takeaway.
开发适合初创企业的健壮服务间通信协议同样需要时间。这些工作都是我们迟早要做的,即使我们的应用程序没有取得巨大成功。这就是关键所在。
That's why, if we would have to do it all over again, I would choose a slightly different approach. In the spirit of moving faster towards a minimum viable product, there is still room to cut back on fancy architecture patterns and built a simpler backend architecture - you guessed it - our old friend, the monolith.
正因如此,如果我们要重新做一遍,我会选择一种稍微不同的方法。为了更快地推出最小可行产品,我们仍然可以简化架构,减少复杂的设计模式,转而采用更简单的后端架构——没错,就是我们的老朋友,单体架构。
During the process of building and deploying a multi-service landscape, we learned a lot about software architecture. We gained valuable insights on how to cut, develop and maintain a multi-service landscape with minimum effort. We also learned a lot from the inter-service communication patterns we designed. However, if your objective is not to learn about the architecture of distributed systems but to efficiently ship a product, it makes sense to opt for a more monolithic approach. If we were to start over, I would choose to build a single system that consists of well-isolated modules with unassuming APIs between them. In place of a separate message broker like rabbitMQ, I would opt for a local event emitter, at least at first. This approach would have saved us a significant amount of time that we could have spent on new features - or for something that we have criminally neglected - marketing.
在构建和部署多服务环境的过程中,我们学到了许多关于软件架构的知识。我们获得了如何以最小的努力切割、开发和维护多服务环境的宝贵经验。此外,我们还从设计的服务间通信模式中受益匪浅。然而,如果你的目标不是学习分布式系统的架构,而是高效地交付产品,那么选择更单一的方法可能更为合适。如果重新开始,我会选择构建一个由良好隔离的模块组成的单一系统,模块之间通过简单的 API 进行通信。在初期阶段,我会选择使用本地事件发射器,而不是像 rabbitMQ 这样的独立消息代理。 这种方法本可以为我们节省大量时间,我们可以将这些时间用于开发新功能,或者用于我们一直严重忽视的营销工作。
PermalinkAbout flexibility 关于灵活性
The most valuable lesson that we learned over the years is that you have to move fast. This applies especially to the early stages of product development. You will almost certainly be wrong about how exactly your product and its features will be received by users. Few ideas will actually end up being successful, at least at first. In that sense, having a high degree of flexibility also means: Whatever you build should be cheap enough to be thrown overboard in case it's not adapted by users, but solid enough so it can be easily improved and built upon in case it is a success. The goal is to spend as little time as possible working on unverified features so that you have as much time as possible to follow up on the ones that your users love.
多年来我们学到的最宝贵的经验是,必须快速行动。这一点在产品开发的早期阶段尤为重要。你几乎肯定会对你产品的功能如何被用户接受有误。很少有想法最终会成功,至少在最初阶段是这样。因此,高度的灵活性意味着:无论你构建什么,都应该足够便宜,以便在用户不采纳时可以抛弃,但又足够稳固,以便在成功时可以轻松改进和扩展。目标是尽可能少地花时间在未经验证的功能上,以便尽可能多地跟进用户喜欢的功能。
Subscribe to our newsletter
订阅我们的新闻简报
Read articles from Appventure Time directly inside your inbox. Subscribe to the newsletter, and don't miss out.
直接在您的收件箱中阅读来自 Appventure Time 的文章。订阅新闻通讯,不错过任何内容。
Written by
In the last few years I have worked with my brother on several private app projects and learned a lot about product and software development. A lot of things that we take for granted today posed big questions for us back then. On this blog we want to share our experiences and learnings with everyone who is interested or who is facing similar tasks.
In my non-free time, I work as a Senior Consultant and Software Architect for INNOQ.
In the last few years I have worked with my brother on several private app projects and learned a lot about product and software development. A lot of things that we take for granted today posed big questions for us back then. On this blog we want to share our experiences and learnings with everyone who is interested or who is facing similar tasks.
In my non-free time, I work as a Senior Consultant and Software Architect for INNOQ.
Published on
Appventure Time
We built a kind of social-networking B2C app. Some things went well, others did not. Some insights that are obvious today caused major headaches in the beginning. Here we share what we've learned.
We built a kind of social-networking B2C app. Some things went well, others did not. Some insights that are obvious today caused major headaches in the beginning. Here we share what we've learned.