Paths Through a Codebase
More Musings and Reflections
Recently, I’ve been thinking about different ways to organize code together into groups. Usually, major groups are formed by functional constraints, like a web application needing a server-side API and a client-side UI. From there, subgroups are formed, anchored to the initial groups in deference to convention.
One important way to group is by layer — similar to above, sometimes informed by constraints and sometimes not). Programming languages expose standard libraries, libraries are built on top, and then applications are built on top of both. Operating systems provide network sockets, programming language standard libraries implement HTTP communication on top of sockets, and developers build client-server applications on top of that HTTP layer.
Often, developers work at the boundary between different groups. That work often looks like reading documentation, configuring other people’s code, or implementing workarounds to combine separate groups of code.
Because codebases are always the end result of past decisions, it’s hard to see passed those boundaries to the reasoning behind them.
Let’s take a look at am example and dig into specific details.
Let’s consider an application in which a client communicates with a server.
A user interface shown in a browser that communicates with a server running somewhere in the cloud. Some code runs in the browser, and some code runs in the server.
So, why the split?
User interfaces that run in a browser are easily accessible from different devices. So, it makes sense to run code in the browser. But, even if all of the code were run in the browser, there still needs to be a server from which the browser can download the code.
So, at the very least, a simple HTTP server is needed to serve the code. And, perhaps some cache configuration to reduce loading times.
Now, what are some reasons for there to be application code run on the server? Put another way, what kind of application code cannot be run in the browser?
If I were to pay money for access to a third party API, I might not want my credentials exposed to users of my application. So, code implementing communication with this third party API would be run on my server. In other words, code that can only be run by the owner of the application needs to not be accessible to the user.
Put another way, private information and code that directly accesses it cannot be exposed to users. This goes for the owner’s credentials. This also goes for data persisted on behalf of a particular user. If that information needs to be private and not exposed to other users, then access to it needs to be handled on the server.
The above constraint necessitates the client-server boundary. Code run on the client is controlled by the user, and code run on the server is controlled only by the owner.
In addition, if a user’s information should only be accessible to that particular user, then an authentication strategy is needed — a constraint that brings along all of the complexities around session token handling.
Now, with the same application, consider code that does batch processing on user data — say, fetching the latest information about a user’s connected bank accounts.
Where should that code run? Well, it can’t run in the browser. So, it should run on the server as that’s currently the only other place?
Remember that currently, code on the server supports code on the client. They are a sort of cohesive unit, kept separate due to some constraints.
Now, code that has nothing to do with the client could also be run on the same server, but there isn’t any constraint necessitating it.
If code having nothing to do with the client were already being run on the server, adding more of it to the same place would be due to convention and not any sort of hard constraint.
Alternatively, a separate server (or service) could be responsible for running the batch-processing code.
In this way, a microservices architecture begins to emerge.
For me, as a developer, I enjoy having minimal constraints and maximal flexibility. I like to understand and own code on both sides of a boundary in addition to on the boundary itself.
I like being able to swap, discard, or rebuild these pieces of code so that I’m not giving up more than I have to.
If nothing else, at least I gain more knowledge and experience.