The first version of this integration had one more piece. Between the facade and the driver sat a Manager, built on Illuminate\Support\Manager, the base class behind Laravel’s session, hashing, and notification drivers. It could choose between drivers by name. The production system this book draws on still has it.
It chose between one driver.
That is an abstraction with one implementation, which is exactly what the preface told you to be suspicious of, and I had written it for the reason the preface warned about: a second provider might arrive one day. It cost four hops for three calls, and a class every new developer had to read to learn that it did nothing yet.
The contract is an abstraction with one implementation too, and it stays. What separates them is cost. The Manager was a layer that ran: every call passed through it. The contract is ten lines that never execute. It lists what the application may ask of a provider, in the application’s own types, and it is the thing a test replaces. It is also how Laravel itself is put together: the framework’s own facades reach services that sit behind the interfaces in Illuminate\Contracts.
It also gives you everything the future needs. If a second provider arrives, you write a second driver and change the few lines that name the first one. If the application then has to choose between them at runtime, that is the day to reach for the Manager, and it will take an afternoon, because the boundary is already in the right place.
Putting It Together
POST /api/licenses
-> StoreLicenseRequest validates, or answers 422
-> LicenseController@store one call, one status code
-> Licenses facade finds the bound driver
-> StatamicDriver calls the provider
-> License typed, or it throws
-> LicenseResource the fields the client may see
-> 201 { "data": { ... } }
Each step has one job and can be tested alone.
Chapter 3 Summary
- If you have an Eloquent model, use it. A typed object stands in for a model only when the data lives somewhere else.
- Turn a provider’s array into a typed object at the edge, and fail there when it is malformed, with your own exception.
- A contract speaks in your application’s types, never the provider’s.
- Configure the HTTP client once, as a macro. One driver per provider, and it throws on failure.
- Never build a provider URL by concatenating something a caller sent.
- Retry only what is safe to retry, and never sleep past your own timeout.
- Bind the driver to the contract and give it a facade. In tests, replace the contract. Add a Manager when there is a second driver to manage, and not before.