Got a desktop application that interacts with some stateful third party web APIs. Its an interactive application, at some stages it has to halt execution and prompt for user input based on responses from said APIs etc..
Application core structure is as follows:
Third Party API Service Class, my abstraction for handling the various REST API calls, transforming request / response data etc... The service class implements a generic Interface, to support replacing the third party service with a mock or different API. For example it could be IWebService which has four or five API calls.
Core application, interacts with #1 via the generic interface, has the "core" application logic to call the API, get user input and so on.
For example (core would be something like):
// Transform API response to a generic set of of options
var options = apiService.Call1(param1, param2)
// Ask user to select an option, and then call the API with the selection Option
var options2 = apiService.Call2(selectedOption)
// And so on
.
.
.
As the APIs are stateful (there is a server side state at the other end and my responses must contain state tokens as well as other limited lifetime metadata).
This leads to strong sequential coupling, my API Service class cannot have its members invoked out of order.
It would be possible to make my API service stateless by having the calls return the state to the caller, and having the caller class pass the previous API state with the next API service call and so on.
However this breaks separation of concerns, my Core Application would then be aware of internal implementation details of this Service class.
So I'm faced with a conundrum:
- I keep sequential coupling, and rely on throwing exceptions at runtime for out of order calls
- I leak internal details (state of Service class) to my caller class and make the application strongly coupled to this particular implementation of the service.
I feel #1 is the acceptable route here as there is no getting away from the fact that the third party APIs have state and 2 is unnecessarily coupling my code.
-
However this breaks separation of concerns, my Core Application would then be aware of internal implementation details of this Service class I don't agree. There is no need for the core application to care about the implementation of the service class or the object it returns. It simply needs to know to pass it back in, which is no more "coupling" than asking the caller to supply ordinary arguments.John Wu– John Wu2020年07月09日 19:50:18 +00:00Commented Jul 9, 2020 at 19:50
-
I guess the issue would be the types of the state objects (and whether they are present at all) would be dependent on the exact implementation of the service class. I do see a way through, if I have a base state class that individual implementations could extend for their specific state needs it could work.boarnoah– boarnoah2020年07月09日 21:02:57 +00:00Commented Jul 9, 2020 at 21:02
2 Answers 2
You can't really get rid of the sequential coupling between the methods of IWebService, because it is imposed upon you by the third-party API. You can either check at runtime if the developer has followed the documentation (your option 1), you arrange your code such that the compiler assists you in detecting attempts to make out-of-order calls, or you invert the dependency.
If you have Call1
return an opaque object that the main program needs to pass as an argument to Call2
, then the application is not strongly coupled to the IWebService implementation. The application gets to know about an extra class, but that could just be declared as an interface without any members. Inside Call2
, IWebService can just cast this argument back to the (hidden) internal type that it really is. This internal type can change as often as needed and the main application won't notice a thing, so the coupling is really low. The only thing you really do is make the sequential coupling very explicit.
As a third possibility, you can invert the whole logic. If the application always needs to make the same sequence of calls to the third-party API, while requesting more information from other sources in between, you can give IWebService just a single method to do the whole interaction with the third-party API. This method could accept one or more callback parameters that IWebService can use to ask the main application for more information when needed.
I would switch out the apiService
class and instead have a apiClient
and apiClientFactory
. That way you can store the state in the client.
var client = apiClientFactory.GetClient();
var options = client.GetOptions();
var selectedOption = ShowUIToSelectOption(options);
client.SetOption(selectedOption);
client.Dispose();
This allows your program to manage state using conventions that should be familiar to most programmers. Depending on your language, it may also offer an obvious and explicit means of defining the point of initialization (construction) and cleanup (disposal or finalization).
-
Thanks for the responses. Could you elaborate more on why using a Factory pattern to instantiate the stateful class addresses the issue? The sequential coupling is due to the state the client (or as I named it Service) has internally. I guess the advantage of explicit disposal is that the service can't be used with old state after its initial use.boarnoah– boarnoah2020年07月09日 20:59:54 +00:00Commented Jul 9, 2020 at 20:59
-
The idea here is that the service requires things to happen in a certain order, but you don't want a developer to have to learn it or care about it. So we use an already familiar and common pattern, one that developers are sure to know, and one that has language constructs (such as
using
) to support it..John Wu– John Wu2020年07月09日 21:13:02 +00:00Commented Jul 9, 2020 at 21:13
Explore related questions
See similar questions with these tags.