3

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:

API

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.

  • Can you elaborate on what ServiceFacade actually does? It's not clear what "a single gateway into all the business logic" looks like. – casablanca Feb 1 at 10:17
  • tbh, this looks a lot like architectural nightmare where someone reads a lot of design patterns and tried to apply all patterns everywhere. There seems to be a lot more architectural component than actual business logic. – Lie Ryan Feb 1 at 14:41
  • @casablanca Let's say this endpoint returns a response with just three fields: last_upgrade_date, available_versions, and expected_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. The ServiceFacade 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." – Ryan Palmer Feb 1 at 19:17
  • @Lie Ryan There's only a couple traditional "patterns" in use here: Abstract Factory and Facade. What I am trying to do is apply SOLID principles, which are different from patterns. I work in a very large and messy codebase full of legacy code, and am constantly blocked by rigid and tightly coupled designs from years past. Even a minor requirement change can require major refactoring and have sweeping side effects. Yes, I do take these principles very seriously and try to adhere as often as possible. I don't think "using a lot of patterns" on its own qualifies an "architectural nightmare." – Ryan Palmer Feb 1 at 19:41
3

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.

It would be more accurate to say that a facade provides a simpler interface to a complex subsystem. A facade composes several low-level operations into a new higher-level abstraction. If you feel the need to segregate the facade into multiple interfaces, then it is likely that the facade is at the wrong level of abstraction.

The typical example of a facade is a Car that wraps an Engine and a Transmission (low-level details) and exposes accelerate and brake operations (simpler interface).

Let's say this endpoint returns a response with just three fields: last_upgrade_date, available_versions, and expected_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. The ServiceFacade provides three methods to call, all in one place, which then invoke the business logic which is scattered around in various places.

Your endpoint itself is the facade here: it is hiding the details of calling the database / external API and exposing a simpler interface consisting of three fields. The ServiceFacade provides no additional value, and in fact, does not have a single well-defined responsibility; continuing to add methods "all in one place" will likely turn it into a god object.

I would eliminate the ServiceFacade and directly inject the relevant repositories / gateways into the endpoints. That should result in a simpler architecture that is more in line with SOLID principles.

|improve this answer|||||
  • Thank you, I agree with your warning about becoming a God Object, and I hadn't considered that the endpoint itself is the facade. I'll explore injecting those dependent business entities into the ResponseAssemblers. That would help flatten the architecture and I don't think I'll be sacrificing anything. – Ryan Palmer Feb 2 at 17:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.