Internal and external integrations

Integrations connect the application generated by Lino to other modules, internal services, and third-party systems. The choice between synchronous calls, in-process integration, HTTP, and events should consider coupling, latency, reliability, and data ownership.

The goal is not just to call another system. It is to declare a boundary: who provides the data, who consumes it, which contract is public, what happens on failure, and whether the operation must be immediate or can be processed later.

Creating integrations

Integrations represent explicit communication with another context, internal service, or external system. They should have a name, contract, authentication, timeout, error strategy, and clear responsibility.

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

Use integration list to check existing integrations before creating another one. Avoid generic integrations such as CommonIntegration, because they tend to accumulate responsibilities without a clear boundary.

Before creating an integration, define which problem it solves: querying an external registry, sending a charge, validating a subscription, synchronizing a user, consuming an internal module, or publishing data to another system. If the integration has no specific intent, it is probably not ready to become a generated contract yet.

  • Name it by the integrated context: prefer Billing, Identity, Catalog, or Shipping over generic names.
  • Separate contract from implementation: the domain and application should not depend directly on transport details.
  • Assume external failures: every remote integration can become slow, unavailable, return a partial error, or change its contract.

Integration resources

Resources represent objects exposed or consumed by an integration, such as customers, invoices, tenants, subscriptions, users, or documents.

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

An integration resource does not need to match the domain entity. It is often a data exchange contract, an external view, or the minimal representation required for communication between contexts. Copying the whole entity into the external contract often leaks internal details and increases the cost of evolution.

  • Model the resource using the vocabulary of the integrated system.
  • Do not expose internal entities as an external contract.
  • Define which fields are identifiers, filters, and returned data.
  • Document pagination, authentication, and call limits when they exist.

When the resource represents data from another module, include only what the consumer needs. The same principle appears in shadow entities: the consuming module keeps a small local copy aligned with its own use case instead of depending on the producer module's full model.

Integration operations

Operations describe actions available on an integration resource: create, query, update, cancel, send, synchronize, or validate.

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

Each operation should define intent, input, output, call method, error behavior, and whether it can be safely retried.

  • Queries should have a timeout and handling for unavailability.
  • Remote commands should be idempotent when possible.
  • External failures should not break the main transaction if the business accepts asynchronous processing.
  • Use logs and correlation id to trace calls between systems.

Idempotency is especially important in write operations. If an attempt fails after the external system has performed the action, a retry may duplicate a charge, create a repeated record, or send a message twice. Whenever possible, use idempotency keys, external identifiers, or documented retry rules.

Consuming integrations

After modeling an integration, resource, and operation, use consumption to connect the application to the generated contract. The goal is to make the use case depend on a named abstraction, with predictable input and output, instead of spreading HTTP calls, URL assembly, headers, response parsing, and error handling across several handlers.

lino integration consume

Use Cases should depend on abstractions, not HTTP details scattered through the code. This makes testing, implementation replacement, and uniform failure handling easier. It also prevents each application endpoint from reinventing timeout, authentication, correlation id, serialization, and error mapping in a different way.

  • Validate data before calling an external service.
  • Use cancellation token and timeout.
  • Map external responses to predictable internal errors.
  • Do not save raw external payload as a domain rule without transformation.

HTTP vs. in-process

In-process integration is simple and fast when modules run together. HTTP creates a more explicit contract between processes, but adds latency, network failures, and the need for resilience.

In-process integration should not mean unrestricted access to everything. Even inside the same runtime, the consumer should communicate through a contract, not through an internal entity, DbContext, or repository from another module. The network cost is lower, but the coupling risk still exists.

ScenarioCommon choiceNote
Modules in the same modular monolithIn-process or internal contractPreserve the boundary; avoid sharing entities and persistence.
Separate service with its own deploymentHTTP or messagingHandle network, authentication, versioning, and unavailability.
Asynchronous and delay-tolerant processIntegration eventDesign idempotent and traceable consumers.

HTTP is more appropriate when producer and consumer have separate runtimes or when you want to make the boundary operationally explicit. In-process is appropriate when the same application hosts the contexts and the response must be immediate, as long as the dependency remains declared through a contract.

Events vs. synchronous integrations

Use synchronous integration when the response is required to complete the current operation. Use an event when the consumer can react later and eventual consistency is acceptable.

This choice is a business decision before it is a technical decision. If an order can only be created after the payment service confirms authorization, the synchronous call may be an essential part of the use case. If creating the order only needs to trigger a notification, projection update, or synchronization to another module, an event tends to be more appropriate.

NeedApproachTechnical concern
Mandatory immediate responseSynchronous integrationTimeout, fallback, predictable error, and stable contract.
Later effect tolerant of delayIntegration eventOutbox, idempotency, retries, and correlation logs.
Frequent reads of a small amount of external dataShadow Entity or local projectionSynchronization, eventual update, and minimal model.
An unhandled error has occurred. Reload ๐Ÿ—™