Cómo un módulo consume la API de otro en Nebula, con tres piezas reales y validadas en dev: Consecutivos (
nebula-masters) como provider,nebula-sharedcomo transporte (WebClients) y Embargos (nebula-treasury) como consumer. Un módulo de negocio nunca accede a las tablas de otro: se integra solo por HTTP a través denebula-shared.
| Rol | Módulo | Qué aporta |
|---|---|---|
| Provider | nebula-masters → ConsecutivoController (/api/v1/consecutivos) |
Autoridad central de numeración (emitir UNICIDAD idempotente / reservar-confirmar-anular ESTRICTO) + catálogo |
| Provider | nebula-accounting-core → terceros, plan de cuentas |
Maestros consultados por otros dominios |
| Transporte | nebula-shared → client/** |
Interfaces @HttpExchange (bloqueante + reactivo) auto-registradas |
| Consumer | nebula-treasury → EmbargoComponent |
Emite consecutivo EMBARGO y valida tercero/cuenta vía los clients |
nebula-shared (reglas)...BlockingClient (RestClient) y ...ReactiveClient (WebClient). Regla inviolable: todo WebClient se ofrece en bloqueante Y reactivo, nunca solo uno.@HttpExchange(url="/<servicio>/api/v1/...") + @GetExchange/@PostExchange/.... Cero implementación, cero beans: los provee NebulaClientsAutoConfiguration cuando nebula.clients.enabled=true.@RequestHeader("Authorization") String authorization. No hay propagación implícita; el invocador decide el token (el JWT del request entrante o uno de servicio para jobs).SuccessResponse<T>; el contenido útil está en resp.getResponse().getContent()./nebula-masters/..., /nebula-accounting-core/...).Ejemplo (provider Consecutivos, método de emisión):
@HttpExchange(url = "/nebula-masters/api/v1/consecutivos", accept = "application/json", contentType = "application/json")
public interface ConsecutivoBlockingClient {
@PostExchange("/emitir")
SuccessResponse<ConsecutivoEmitidoResponse> emitir(@RequestBody EmitirConsecutivoRequest req,
@RequestHeader("Authorization") String authorization);
// ... read/get/create/update/delete/suspender/reactivar, reservar/confirmar/anular,
// tipos, page/get-record/page-response (espeja TODO el controller)
}
En EmbargoComponent se inyectan los clients de nebula-shared y se les pasa el token del request:
private final ConsecutivoBlockingClient consecutivoClient; // -> nebula-masters
private final TerceroBlockingClient terceroClient; // -> nebula-accounting-core
private final PlanCuentaContableBlockingClient cuentaClient; // -> nebula-accounting-core
private String auth(HttpServletRequest request) { return request.getHeader("Authorization"); }
long emitirConsecutivo(Long deudor, String orden, String auth) {
EmitirConsecutivoRequest req = EmitirConsecutivoRequest.builder()
.codigo("EMBARGO") // def existente en masters (tenant)
.claveIdempotencia(deudor + ":" + orden) // idempotencia de negocio
.contexto("embargo").build();
return consecutivoClient.emitir(req, auth).getResponse().getContent().valorNumerico();
}
Reglas del consumo:
status.claveIdempotencia de negocio (aquí deudorTerceroId:ordenJudicial): un reintento no duplica el número.Front ── POST /nebula-treasury/api/v1/embargos/create (Bearer JWT) ──▶ EmbargoController
└─▶ EmbargoComponent.create(dto, request)
1. validarCondicionales(dto) [validator local: tipo/ordenado]
2. terceroClient.validarActivo(deudor, auth) ──▶ accounting (RN-ALC2-05)
3. terceroClient.validarActivo(acreedor, auth) ──▶ accounting
4. cuentaClient.validarMovimientoActiva(cuenta, auth)──▶ accounting (RN-ALC2-06)
5. countByDeudorTerceroIdAndOrdenJudicialAndTenant() [nativo + tenant] (RN-ALC2-01)
6. consecutivoClient.emitir(EMBARGO, clave, auth) ──▶ masters → valorNumerico=N
7. embargoRepository.save(entity) [tenant por @PrePersist]
◀── 200 SuccessResponse<EmbargoDto> (client_id/company_id del token)
Evidencia de la corrida en dev (jun-2026): dos create consecutivos obtuvieron consecutivo = 5 y 6 emitidos por masters (def EMBARGO), persistidos con client_id=7 / company_id=5; /listado devolvió el listado paginado con nombres de tercero enriquecidos desde accounting (el /index quedó como probe de disponibilidad del servicio); levantar llevó el estado a LEVANTADO; delete respetó la regla "solo INACTIVO" (409 sobre ACTIVO, 204 sobre INACTIVO).
get-record/page-response); internamente cumple todo el patrón (multitenant nativo, vista @Immutable, etc. — ver 09).nebula-shared publica ambos clients (blocking + reactive) que espejan exactamente el controller, con envelope SuccessResponse<T> y token explícito.