生成代码之前的架构决策
Lino 可以快速生成大量代码,但结果的质量取决于生成之前所做的决策。选择限制、项目类型、沟通策略和可接受的技术债务务水平可以防止最初的生产力变成返工。
使用此页面作为架构清单,在创建高级服务、模块和功能之前调整您的团队、领域和操作。
项目类型
跑步前 lino project new,定义系统的目标。内部管理项目、多tenantSaaS、公共API和分布式平台有不同的需求。
- 简单应用: 上下文少、部署单一且对操作独立性的需求低。
- 模块化单体: 多个模块在同一流程中,边界明确,运营成本更低。
- 微服务: 服务分离,独立部署,异步通信,操作复杂度更高。
- SaaS: 需要tenant隔离、强大的安全性、秘密、rate limiting、可观察性和明确的计费/运营决策。
该决定还定义了第一天应该出现哪些问题。 SaaS 不能将tenant视为迟到细节;如果没有rate limiting和明确的合约,公共API就不可能诞生;具有事件的解决方案需要考虑 Outbox、幂等性和可观察性;具有多种文化的应用程序应该从一开始就避免分散的字符串。
创建基础时,记录主要选择:支持的文化、分布式缓存、异步通信、代码分析器、数据库类型、Web 应用程序存在、身份验证、tenant和工作人员。这些选项会影响包、模板、AppHost、参数、secrets、生成的项目以及稍后更改架构所需的工作。
模块化与模块化整体式微服务
模块化整体结构允许您将域组织成具有明确内部契约的模块,从而使构建、调试和部署更加简单。微服务只有在有充分理由时才会获得回报:独立的规模、独立的团队、稳定的业务边界或不同的运营需求。
从模块化单体开始并不意味着忽略架构。这意味着在域仍在被发现时保留简单的部署,但创建内部限制以防止所有内容成为单一的实体、服务和共享表。这就是“从简单开始”和“从没有结构开始”之间的区别。
| 标准 | 模块化整体结构 | 微服务 |
|---|---|---|
| 部署 | 最简单,通常是独特的 | 按服务独立 |
| 一致性 | 更容易维护本地交易 | 需要事件、Outbox 和最终一致性 |
| 手术 | 降低基础设施成本 | 更高的可观察性、队列、重试和自动化 |
| 重构 | 一开始比较便宜 | 当合同已经公布时会更加困难 |
Lino 支持这两种路径。该决定必须遵循团队的领域和运营能力,而不仅仅是技术偏好。微服务需要部署自动化、分布式可观察性、合同版本控制、网络容错、消息传递、重新处理和数据治理。如果团队还无法安全地操作此操作,那么过早分发通常会增加风险。
当首要任务是了解领域、验证产品并保持操作可控时,请使用模块化整体架构。当操作独立性已经成为具体需求时使用微服务:独立的规模、拥有真正所有权的团队、独立的发布周期或系统各部分之间不兼容的基础设施要求。
服务和模块限制
良好的限制可以减少耦合并明确谁拥有每条规则。避免为每个表或每个屏幕创建服务。服务和模块必须代表业务能力。
- Catalog: 产品、类别、商业信息和销售咨询的数据。
- Sales: 订单、购物车、应用定价、快照和商业流程。
- Stock: 可用性、预订和移动。
- Security: 用户、角色、权限、身份验证和令牌。
一个好的限制有自己的语言、一起变化的规则、明确所有权下的数据以及与其他上下文进行通信的明确契约。错误的限制通常表现为“公共”模块、多个区域共享的实体、每个人都查阅的表或仅仅因为出现新屏幕而创建的服务。
当一个模块需要来自另一个模块的数据时,可以在 API、集成事件、Shadow Entity或边界重塑之间进行选择。直接访问另一个模块的库是结构耦合的标志。即使两个模块在同一进程中运行并使用同一物理数据库,模式、持久性项目和契约也必须保护业务边界。
- 良好的极限标志: 该模块可以发展其实体和迁移,而不会由于内部细节而强制另一个模块重新编译。
- 弱阈值信号: 对实体的简单更改需要检查不应该知道该实体的屏幕、查询和模块处理程序。
- 隐藏的依赖符号: 消费者需要对表格、技术领域或生产者的内部规则“了解太多”。
自觉的技术债务务
技术债务务不仅仅是任何缺陷。这是在了解未来成本的情况下推迟质量的决定。问题在于产生的债务没有名字、没有所有者、也没有付款计划。
在 SaaS 或模块化系统中,最危险的技术债务务不仅仅是长方法。限制发展的是债务:无法单独更改的模块、没有可靠隔离的tenant数据、存储库中的秘密、没有限制的敏感端点、没有重新处理的事件或没有明确合同的集成。
- 生成和自定义过程中使用的文档快捷方式。
- 在进入下一代阶段之前运行构建和测试。
- 避免修复因重复的本地黑客攻击而产生的问题;当图案错误时调整图案或模板。
将每个架构决策视为可审计的。如果您选择不启用消息传递,请注意当前流程不依赖于异步处理。如果您选择每个tenant列,请确保过滤器和测试。如果您选择同步集成,请假设超时、不可用性和契约错误是设计的一部分。
沟通策略
根据一致性、延迟、可追溯性和耦合性的实际需求选择通信。主要问题不是“使用哪种技术?”,而是“生产者现在需要答案,消费者是否应该取消操作以及谁拥有数据?”。
在模块化系统中,最常见的错误是将通信转换为隐藏的依赖关系:一个模块查询另一个模块的表、重用内部实体、注入外部存储库或创建同步调用而不处理失败。乍一看,这似乎很有成效,但它使后续的每次更改都变得更加危险,因为边界不再明确。
- 同步调用: 有利于立即咨询和可接受的依赖性。避免进入长瀑布。
- 领域事件: 相同应用限度内的内部反应。
- 整合事件: 模块、服务或系统之间的通信,通常使用 Outbox。
- Shadow Entity: 用于咨询和验证的最低本地副本,无需直接依赖于生产商。
- 外部 HTTP 集成: 适用于第三方服务,具有超时、重试、日志和故障处理功能。
如果规则需要立即更新,请使用同步流或修改阈值。如果您接受最终一致性,则更喜欢使用事件来减少耦合。当消费者只需要从另一个上下文读取一小部分数据时,Shadow Entity可能比暴露整个原始实体更安全。
| 问题 | 推荐方向 |
|---|---|
| 完成用例是否需要响应? | 显式同步集成,具有超时、可预测错误和明确的契约。 |
| 即使消费者不在,生产者也应该确认交易吗? | 与Outbox集成事件和异步处理。 |
| 消费者是否只需要验证或查阅最少的数据? | 由事件或集成提供支持的Shadow Entity,具有阅读模型的本地所有权。 |
| 更改是否发生在相同的聚合或限界上下文中? | 域事件或域本地逻辑,无需发布不必要的外部合约。 |
