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, orShippingover 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.
| Scenario | Common choice | Note |
|---|---|---|
| Modules in the same modular monolith | In-process or internal contract | Preserve the boundary; avoid sharing entities and persistence. |
| Separate service with its own deployment | HTTP or messaging | Handle network, authentication, versioning, and unavailability. |
| Asynchronous and delay-tolerant process | Integration event | Design 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.
| Need | Approach | Technical concern |
|---|---|---|
| Mandatory immediate response | Synchronous integration | Timeout, fallback, predictable error, and stable contract. |
| Later effect tolerant of delay | Integration event | Outbox, idempotency, retries, and correlation logs. |
| Frequent reads of a small amount of external data | Shadow Entity or local projection | Synchronization, eventual update, and minimal model. |
