Domanda

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.

È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top