carlosgude/integration-engine
IntegrationEngine forces every Symfony integration into the same predictable shape: each endpoint is just a Request and a Response, with facades, mappers, and YAML action maps generated for you. No ad-hoc HTTP clients or scattered mapping logic.
Build vs. buy for the integration layer. This package is a "buy the pattern, build the adapters" play. It does not give you integrations — it gives you a mandated shape for them (Action → Mapper → Response, one YAML action map per integration). The decision it unlocks: do we standardize how every third-party API enters our codebase, or keep letting each squad improvise? If the roadmap includes 3+ external APIs over 12 months, a convention is cheaper than coordination meetings.
Integration roadmap sequencing. The make:integration scaffolding command effectively prices a new integration as "one Action + one Mapper + one Response + a YAML entry." That's a defensible unit-cost input for planning: you can commit to "N integrations per quarter" with more confidence than if each one is a fresh design exercise. It also de-risks handing integrations to less senior engineers.
Vendor-swap optionality. The prescribed Anti-Corruption Layer (Gateway) is the real product asset. It means the decision "which CRM / payment provider / KYC vendor do we use" becomes reversible at bounded cost, because exactly one class knows both the vendor DTO and the domain model. That is a roadmap-level hedge, not a code-style preference — it belongs in the same conversation as multi-vendor fallback, regional providers, and pricing renegotiation leverage.
Ship-or-defer on OAuth2-based integrations. Dynamic authorization (token action, cached, injected as static auth, auto-eviction and single retry on 401) removes a recurring prerequisite project — "we need token management infrastructure first." That usually unblocks a whole category of feature work (marketplace connections, customer-authorized data pulls) that would otherwise sit behind platform work.
Bulk and dashboard features. sendMany() gives parallel fan-out with per-key success/failure isolation. That directly supports product surfaces like "sync 500 contacts," "refresh all connected accounts," or admin health dashboards — where one failing record must not fail the batch. Worth noting the constraint: real concurrency requires the REST client or a custom BatchClientInterface — the GraphQL adapter does not batch in parallel.
Reliability and rate-limit posture as a first-class feature. The middleware pipeline (rate limiting, retries, circuit breaking) means "we will not get banned by vendor X" and "we degrade gracefully" can be accepted as product requirements rather than aspirational engineering tickets.
Support-cost attribution when a customer reports slowness (new in v9.4.0). The new ResponseReceived lifecycle event cleanly separates the vendor took 4 seconds from our mapper took 4 seconds: ResponseReceived fires when the client returns a response and before mapping, so ResponseMapped::timestamp − ResponseReceived::timestamp is our own processing time and the rest is resolution + auth + middleware + transport. That turns "integration X is slow" from a two-day investigation into a dashboard query, and it gives you evidence for vendor SLA conversations rather than a hunch. ResponseReceived followed by RequestFailed is also a distinct, diagnosable signal: the vendor answered correctly and our mapping broke — a different owner, a different ticket, and a different fix than a transport failure. This is the kind of instrumentation that decides whether you renew or escalate a vendor, so it's worth capturing as a product requirement on new integrations rather than an afterthought.
Honest friction point: the framing here is Symfony, not Laravel. There is no Laravel service provider or Laravel-shaped config story. If the roadmap runs on Laravel, this package is not a candidate without an adapter layer — that is a hard gate, not a trade-off.
Adopt when most of these hold:
ResponseReceived event; previously you would have had to instrument each adapter yourself.Look elsewhere when any of these hold:
Practical adoption path: pilot on one low-risk, read-only integration. Wrap it behind a facade and a Gateway from day one. That gives you a real cost baseline and a clean exit — the package's own architecture makes it replaceable, which is unusual and worth exploiting.
To executives — the business case in five lines:
To engineering — why it earns its place:
RequestSent → ResponseReceived (resolution, auth, middleware, transport) → ResponseMapped (mapping). Emitted for send() and for each sendMany() item, so a slow batch key is identifiable.sendMany() for parallel fan-out with per-request failure isolation, keyed results.rest, graphql, custom ClientAdapterInterface) and PathResolvableContextInterface for non-trivial query strings — the abstraction bends rather than breaks.debug:integration was just fixed for the v9.3.0 defaults: YAML section, so introspection works on current-format configs.The caveats to state plainly, not bury:
HttpResponseReceived; the v8 rework dropped it; v9.4.0 has restored the same capability as ResponseReceived. That is healthy maintenance, but it is also a signal: anything asserting an exact event sequence — custom listeners, dashboards, alerting rules — can shift between minor releases. Test against event presence, not event order, and re-read the changelog before bumping.Recommendation: approve as a piloted standard — one integration, one quarter, measured against a control integration built without it. Success criteria: time-to-first-working-integration, lines of glue code, whether a deliberate API change stays inside one class, and — now that the tooling supports it — whether a "this integration is slow" question can be answered from telemetry without a code investigation.
How can I help you explore Laravel packages today?