内部和外部集成

集成会将 Lino 生成的应用程序连接到其他模块、内部服务和第三方系统。选择同步调用、in-process 集成、HTTP 或事件时,应考虑耦合、延迟、可靠性和数据所有权。

目标不只是调用另一个系统,而是声明一个边界:谁提供数据,谁消费数据,哪个契约是公开的,失败时会发生什么,以及操作必须立即完成还是可以稍后处理。

创建集成

集成表示与另一个上下文、内部服务或外部系统的显式通信。它们应具有名称、契约、身份验证、timeout、错误策略和明确职责。

lino integration new --name <ServiceName>
lino integration list

在创建另一个集成之前,使用 integration list 检查现有集成。避免使用 CommonIntegration 这类通用集成,因为它们往往会在没有清晰边界的情况下累积职责。

创建集成之前,先定义它解决的问题:查询外部注册表、发送收费请求、验证订阅、同步用户、消费内部模块,或向另一个系统发布数据。如果集成没有明确意图,它可能还没有准备好成为生成的契约。

  • 按被集成的上下文命名:优先使用 BillingIdentityCatalogShipping,而不是通用名称。
  • 分离契约和实现:领域和应用不应直接依赖传输细节。
  • 假设外部失败:任何远程集成都可能变慢、不可用、返回部分错误或更改契约。

集成 resources

resources 表示由集成暴露或消费的对象,例如 customers、invoices、tenants、subscriptions、users 或 documents。

lino integration resource new --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino integration resource list --service <ServiceName> --module <ModuleName> --entity <EntityName>

集成 resource 不需要等同于领域实体。它通常是数据交换契约、外部视图,或上下文之间通信所需的最小表示。将整个实体复制到外部契约中,通常会泄露内部细节并增加演进成本。

  • 使用被集成系统的词汇来建模 resource。
  • 不要把内部实体作为外部契约暴露出去。
  • 定义哪些字段是标识符、过滤条件和返回数据。
  • 在存在分页、身份验证和调用限制时进行文档说明。

当 resource 表示另一个模块的数据时,只包含消费者需要的内容。同样的原则也出现在 shadow entities 中:消费模块保留一个小的本地副本,与自己的 Use Case 对齐,而不是依赖生产模块的完整模型。

集成操作

操作描述集成 resource 上可用的动作:创建、查询、更新、取消、发送、同步或验证。

lino integration operation new --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino integration operation list --service <ServiceName> --module <ModuleName> --entity <EntityName>

每个操作都应定义意图、输入、输出、调用方式、错误行为,以及是否可以安全地重新执行。

  • 查询应具有 timeout,并处理不可用情况。
  • 远程命令应尽可能具备幂等性。
  • 如果业务接受异步处理,外部失败不应破坏主事务。
  • 使用日志和 correlation id 跟踪系统之间的调用。

幂等性在写操作中尤其重要。如果外部系统已执行动作之后某次尝试失败,重试可能会重复收费、创建重复记录或发送两次消息。尽可能使用幂等键、外部标识符或文档化的重新执行规则。

消费集成

在建模集成、resource 和操作之后,使用消费功能将应用程序连接到生成的契约。目标是让 Use Case 依赖一个具名抽象,具有可预期的输入和输出,而不是把 HTTP 调用、URL 组装、headers、响应 parsing 和错误处理分散到多个 handlers 中。

lino integration consume

Use Cases 应依赖抽象,而不是散落在代码中的 HTTP 细节。这会让测试、实现替换和统一的失败处理更容易。它也避免每个应用 endpoint 以不同方式重新发明 timeout、身份验证、correlation id、序列化和错误映射。

  • 在调用外部服务之前验证数据。
  • 使用 cancellation token 和 timeout。
  • 将外部响应映射为可预期的内部错误。
  • 不要在未经转换的情况下把原始外部 payload 保存为领域规则。

HTTP vs. in-process

当模块一起运行时,in-process 集成简单且快速。HTTP 会在进程之间创建更明确的契约,但会增加延迟、网络故障以及对韧性的需求。

in-process 集成不应意味着可以自由访问一切。即使在同一个 runtime 内,消费者也应通过契约通信,而不是通过内部实体、DbContext 或另一个模块的 repository。网络成本较低,但耦合风险仍然存在。

场景常见选择说明
同一个模块化单体中的模块In-process 或内部契约保留边界;避免共享实体和持久化。
拥有独立 deployment 的独立服务HTTP 或消息传递处理网络、身份验证、版本控制和不可用。
异步且可容忍延迟的流程集成事件设计幂等且可追踪的消费者。

当生产者和消费者具有不同 runtimes,或希望让边界在运维上显式时,HTTP 更合适。当同一个应用托管这些上下文且响应必须立即返回时,in-process 是合适的,但依赖关系仍必须通过契约声明。

事件 vs. 同步集成

当需要响应来完成当前操作时,使用同步集成。当消费者可以稍后响应且 eventual consistency 可以接受时,使用事件。

这个选择首先是业务决策,其次才是技术决策。如果订单必须在支付服务确认授权之后才能创建,同步调用可能是 Use Case 的核心部分。如果创建订单只需要触发通知、投影更新或同步到另一个模块,事件通常更合适。

需求方法技术注意事项
必须立即响应同步集成Timeout、fallback、可预期错误和稳定契约。
可容忍延迟的后续效果集成事件Outbox、幂等性、retries 和关联日志。
频繁读取少量外部数据Shadow Entity 或本地投影同步、最终更新和最小模型。
发生了未处理的错误。 重新加载 🗙