In my project, we have a couple different back-end APIs/endpoints that are called by the same front-end page at different times. All of these endpoints are sort of related to the overall "theme" or "purpose" of the page, but they are called at different times, for different reasons, and return different data. The data returned by each endpoint varies a lot. Some fields require simple database queries, some require processing to generate, some require calls to external APIs, but they all serve this common "theme" of the page and are temporally coupled within their respective endpoints.
Here's the general architecture of ONE of these endpoints, UpgradePreviewEndpoint
. This one gets called when the user selects a target version that they wish to upgrade to:
The other endpoints follow the exact same architecture and share the same abstractions, so hopefully imagination will suffice. Each endpoint has a single process
method that takes a request object. The endpoint then asks an IResponseAssembler
to assemble a string response. The format of that response is irrelevant to the question, but currently it's JSON by default.
Underneath the response assembly component, I have a single gateway into all the business logic used by this page, regardless of which endpoint is accessing it. This is my facade, which is called ServiceFacade
in the diagram. Each concrete IResponseAssembler
implementation also has its own corresponding I...Facade
interface through which it interacts with the concrete ServiceFacade
. The ServiceFacade
implements all of these interfaces, one for each assembler.
Since there's only one concrete facade, and it's highly stable, I could have allowed all of the assemblers to depend on it directly. I could have also created a single interface for the facade and allowed all of the assemblers to depend on that one interface. However, since each assembler only needs to call a few methods on the facade, both of those approaches would violate the Interface Segregation Principle. What I've tried to do here is implement the ISP by using individual interfaces to carve out pieces of real estate inside the ServiceFacade
for each assembler to depend on. That way, none of them depend on anything they don't actually need.
However, I have seen many descriptions of the Facade Pattern state that it should provide a single interface into a complex subsystem. Segregating the interface seems to violate that axiom. On the contrary, splitting the concrete ServiceFacade
class into multiple entities seems like a really absurd idea in this case, since the only reason for having multiple endpoints is the temporal coupling of the GUI which is completely irrelevant at the service layer.
I would like the fine folks in this community to scrutinize this design and tell me if I am committing any egregious crimes, or if there is a better approach. This is not my first iteration with the design of these APIs, and I feel that I am on the right track now, but that feeling seems to get me into trouble often enough to warrant skepticism.
ServiceFacade
actually does? It's not clear what "a single gateway into all the business logic" looks like.last_upgrade_date
,available_versions
, andexpected_duration
. First one is a database query, second one is a call to an external API, and third is a process that includes querying and additional processing. The actual business logic for these pieces exists in various places in the code base. TheServiceFacade
provides three methods to call, all in one place, which then invoke the business logic which is scattered around in various places. Hence, "single interface to a complex subsystem."