Over time I’ve seen a lot of companies create mobile or web applications that integrate with existing legacy systems. Most of the time they either use existing services as is and leave the app developers to make sense out of the legacy domain or they don’t put enough thought into the context in which the new applications will operate when creating an API for the frontend team.
The problems that surface from this dilemma are typically handled in two ways:
- The details of the legacy system bleed into every part of the application making things more complex and difficult to manage
- Creation of an Anti-Corruption Layer inside the app(s) that translates between the separate Bounded Contexts
While #2 is a much better option than #1, this still results in duplication on each platform to handle the model translation. On top of that, it creates issues where the iOS, Android, and Web apps have differing logic making it more difficult to identify issues and find their root cause.
A lot of people make the case that this problem can be solved by using “cross-platform” technologies to build out the client applications, so you’d only have to implement the logic once. While in theory this sounds really nice, in practice you are forgoing a lot of the available power afforded by the native platforms, increasing risk due to the additional layers and dependencies, and often causing enough pain and slowdowns that you might as well have implemented it natively anyway.
What I have seen work quite well and propose to become more of a standard way frontend teams operate, is to move this anti-corruption layer to a shared service in the form of an API to the rest of the available legacy services. This means that the API becomes your cross-platform code that maintains a consistent set of model entities and logic across all apps. The UI and other hardware/platform interactions are the only things handled natively. This takes advantage of the full power of each client platform and lowers the overall cost and risk of implementation. The API shouldn’t just be a translation layer either. You will want to push every possible piece of logic to the API that you can, further limiting duplication and variation among the clients.
For this approach to work effectively though, you will need your app developers to work closely together and have shared ownership of the server API in which it interacts. What this means is that developers become full-stack. If an “app developer” starts to write client logic that is common between all platforms, they stop, pull up the server API project, and write the code there instead. With this single common implementation all other platforms building against that API will benefit, reducing time of overall implementation and reducing the chance for different behaviors on each platform. We are now treating the API code as if it’s a shared library living within the application itself.
One implication of this approach is that data integrity issues should be handled by the server as well, not the client. Sure, this might mean that your client could crash if it encounters unexpected data, but I would argue that's a good thing. Your crash reporting dashboard will light up and it’s hard to ignore. The good thing here is that you can (and should) fix the issue on the server side and deploy it quickly, without the need to push new client builds out and wait for that deployment to reach all of your users.
No Silver Bullet
Of course there will be some types of apps that this strategy isn’t well suited for. Off the top of my head, anything that is primarily an offline experience, like games, would not benefit as much from this approach. We’re mainly talking about “connected” apps that aren’t very useful without talking to the internet. It doesn’t mean that you need to avoid this approach if you need some kind of offline functionality, like caching, read-only views, etc. You can still push everything possible to the server and implement only what’s required locally. It’s not an all-or-nothing choice. That being said, I would say that you at least want the API to define the frontend model, even if you can’t put all the logic there.
Hopefully you’ll think more about this and advocate for shared ownership of the full stack if you work on or with frontend development teams. The great thing here is that you will only implement your model and most important business logic in one place. This has the benefit of reducing overall development time, reducing the chance for slightly different implementations on each platform, and lets you more easily change the business logic without having to update the client code. This also means that you need developers on your team that are willing to go full-stack and not limit themselves to a single platform or language. I would argue that the good developers out there want to learn and be involved in more things anyway.