In November of last year, I attended a meetup in Utrecht for the global day of coderetreat. During the lunch break, there was a talk by Joost Baas from bol.com, a large Dutch eCommerce company.
In the talk, Joost discussed a change they were making to how order statuses work. They integrated with the delivery firms to provide the shipping status within the bol.com interface. They added some new possible states an order could be in. This change rippled through different teams; from the backend where the new state was introduced (possibly even multiple microservices within the backend), to the web and mobile frontend which had to handle the new state to display it in a user-friendly way. Each of these had to add a new check for these values.
They started to wonder if they could avoid rippling effects from changes like this in the future. So what they ended up doing was making the API provide the status label, description and any other info that the frontend would need to display. The UI would then provide places where these values would be displayed.
Coherence and Decoupling
The talk reminded me about a blog post on code layout I wrote earlier last year. The post argues for grouping code by application domain (users, blog posts, permissions, ...), rather than architectural component (models, views, controllers, repositories, ...). Code dealing with a certain part of the problem domain is often necessarily coupled together. There are lots of definitions for coherence out there, but I think of application parts being coherent if they have an inherent reason to change together. So grouping coherent code together often confines changes to a specific part of the code base.
The Chasm Between the Client and the Server
In applications with a client-server architecture (of which web applications are probably the most common), by definition, the code runs in 2 separate areas, the client and the server.
But wait..., client and server are architectural components, with components belonging to the same domain area, split between the 2 sections of code. Worse yet, these are often completely separate repositories, written in different programming languages, managed by different teams! This leads to a situation where cascading changes are quite common, but hard to coordinate.
Solutions?
This is not one of those blog posts where I have a magical solution in the conclusion. I have a few guesses for what might work, and I'd love to hear from you about your experiences.
If all the parts of your application are written in the same language, you can share a lot of data structures and logic between the client and server, isolating the code that is tailored to a specific platform.
Have you tried splitting your code by domain area rather than client/server? (How) did that work?
Top comments (14)
The split between front/back-end is an artificial and dangerous one when it comes to code control. There's simply no way to be an effective front-end developer and work strictly on code in the "front-end" layer.
Changes in UX require changes throughout the code. A project that keeps programmers out of the different domains will struggle to improve their UX.
We've seen the same thing with DevOps. If you try to draw an artificial line between what is administration and what is programming, you end up with bottlenecks and problems. There's no problem with have domains of focus, but the idea that you're isolating the code is silly.
I talk about a new definition of a developer in my book. I stand behind more people needing to understand more parts of the project in order to get quality software. Isolation, walls, boundaries, all create problems instead of solving them.
"There's simply no way to be an effective front-end developer and work strictly on code in the "front-end" layer."
I have the feeling solutions like GraphQL change this, by simply giving the front-end more control.
It probably does. But at the same time, having a very open protocol can make maintenance hard. Your backend no longer has any idea how it's really being used. There aren't specific APIs to optimize, nor specific unit tests for essential pathways.
While the clients are more flexible you can still check what is accessed together in your monitoring and optimize your resolvers and data-stores for it when needed.
I'd even say that the GraphQL spec (which is stricter than the REST spec) allows for optimizations across different implementations of that spec.
Agreed. So how do you usually go about making changes across frontend and backend as easy as possible?
Often I'd start with the feature I want in the front-end, play with it until I recognize what I need in the back-end.
Then implement the back-end feature first. I'll test it with unit tests, with the exact use-case I need in the front-end. This keeps the two bits separated, meaning it works fine with split repos, even with separate release schedules.
Once the back-end is ready, I'll implement the front-end part of the feature.
If you have one repo for front/back-end this is painless. The more repos you have, the more release pipelines you have, the more involved it can be. You need to ensure the backend support is released before the front-end -- though honestly, that's more of a nuance than significant problem.
Note, I'm not perfect at knowing exactly what the back-end feature is that I need. So I will usually work front/back at the same time until I do, then focus on finishing the back-end feature. This requires your code be able to work with working code from multiple repos -- an essential setup need for multi-repo projects.
This is actually something I've thought about a lot, it sounds great in theory to split by app domain instead of client/server, but the question I always come back to is... would it ruin the portability of my app?
I feel like in today's world (and especially in tomorrows world when internet is practically omnipresent), users expect apps and services to be available wherever they are, whether they're on a phone, tablet, or computer, whether they're at home, in their car, or at work, whether they're talking to Alexa, Google, or Siri, and eventually, whether they're in the physical world, the augmented world, or the virtual world.
With so many different interfaces out there I feel like I should be designing apps to be as adaptable as possible to new devices and operating systems, and I wonder if coupling the backend with the front end too tightly would just end up making life harder in the long run.
What are your thoughts on that?
I actually think making your business logic one sharable unit makes your app more portable. The UI just calls into that shared logic. Need to port it to a new platform? Just write a new interface layer.
Have a look at the ports and adapters architecture, also known as hexagonal architecture. All platform-specific things would just be adapters.
This is something that I've been trying to do with my apps. The business logic and models are written in a language that can be moved between platforms with little to no changes (I'm currently using C#). Then, I just write up a front-end that follows the established rules of the platform while providing the same functionality across the board.
I haven't deployed it yet, but it's made "porting" so much easier.
Wow. I didn't have time to take a detailed look at the whole article yet but just looking at the first graphic gives me a good feeling about it. I've actually kind of thought about structuring apps similar to that before but I never knew there was a whole pattern for it already out there. Amazing!
So in that case would you still have a small client/server split but just build a backend 'port' that kind of understands more about what the frontend needs? So if you were serving say a react app on the front end it could access a specific route on the server layer that would return information more curated towards that specific interface, instead of just generic JSON objects?
I think... sort of.
But interestingly, I feel like some of the environments which abstract away distinctions between client and server sometimes fail to optimize for the benefit of the user.
With technologies and APIs available these days, you can do some pretty cool things if you pay more attention to what constraints are (less abstraction) vs buying too much into frameworks that abstract the client/server model away too much.
No perfect answer, but definitely a good question in these times.
If the front-end and back-end are tightly bound, then a monorepo might help.
In a perfect world things wouldn't be so heavily dependent on each other, but who lives in a perfect world?
We use front/back software components, but the same person usually implements it from end to end. From being involved in customer discussions to implementation to post-deployment support. They ask for help as needed from whoever knows what they are unfamiliar with.
Fresh devs, we tend to start on UI with some scaffolding done for them. But when they feel ready, they expand into the rest of it.
To answer your question, I don't feel that applications are split the wrong way as client/server, but it could be that teams split that way become problematic.