User Interfaces as their name implies are the bridge between humans and computers.
As software developers, it’s our duty to build the user interface as user-friendly and as consistent as humanly possible, making this bridge easy to cross. A lot of the time, eye-catching details are not essential to building better experiences. They can be added as the interface evolves. For the user, the main focus is set on the core functionality that interfaces are capable of exposing.
There is a long process through which interfaces evolve, but the business logic remains the same. A minimum viable product would implement these core functionalities without any flares that would distract the user from reaching their goal. The way the user interacts with an interface will evolve over time since humans are capable of learning new patterns, but the business side of the UI will be less prone to changes.
When developing software, as Uncle Bob suggested, we should consider stakeholders as owning parts of our business logic. For example, a business analyst might own how certain math calculations are made for a shopping cart, while the shape of the payload we send to the API is owned by the backend engineers, and so on. How users interact with our system lands at the UXer’s feet, while we, the frontend developers out there, have to manage complex forms, tables, or page navigations to accommodate the end-user.
Our focus is to bring a lot of systems together and unite them under one UI. From translating complex user interactions or requirements to showing optimistic updates by recreating parts of the backend logic, the business logic of the UI application is the core on which the actual view layer can later be developed on. Regardless of the platform you are targeting, whether it’s mobile, native, or web the central part of the application, it should have minimal changes to accommodate them.
In this sense, I strongly encourage everyone to enlist a train of thought when it comes to deciding where a certain piece of code belongs.
In my React applications, my rule of thumb is to separate the two types of logic in application state and domain. The application state holds logic that is needed for the view layer (for example if a modal is opened), while the domain handles data processing or how certain modules or services coexist. All of the exchanges that happen at the domain level, that the view layer should be aware of, eventually end up exposed as application state.
No matter what we use as the state management solution, having this boundary between the business logic and the interface (referred to as the view layer above) helps us in separating both stakeholders and concerns.
But What About the Developers’ Experience?
I would like to focus more on this subject, of developing business logic without having to worry about the interface side of it. In absence of an interface, developers have been using unit testing to validate that the business logic will perform as expected. While there are certain advantages associated with this, when working on a startup, prototype, or proof of concept this time spent is not wisely managed.
What I’m proposing is a different approach to developing applications focused more on a simple principle where the business takes the main stage, while the interface will be an effect of what happens there. In this sense, I devoted a lot of research on finding a tool that has this capability.
In my findings, overmind just blew my mind.
I fell in love with what it offers – a DX-first tool that can manage the state and work with any UI library or framework. Combine that with its Typescript capabilities and we have the best experience a developer can dream of. Let’s dive into that promise of better developer experience.
Above, there is a screenshot of the overmind developer tools. You can connect to multiple applications to debug them, so in the first item of the sidebar, you have this “context” switcher. The green chrome-like icon is the connection status, which is done over WebSockets. Next, you have the current state, which you can view or edit.
The gear icons are the “Actions tab” that lists all of the possible actions. It is best practice to use the domain to namespace those. In this tab, you will spend most of your development time. Here you can test different actions just as you would do test-driven development.
Fast Prototyping Is Key
You know when you start writing tests, as you add more and more of them it becomes a burden. With overmind, you could easily get away with snapshots, but when you are working on a time-sensitive feature or product, you should have most of the testing done in the developer tools. As you progress with the development of the application, add more actions and see how they affect the state when running them in different cases.
A piece of advice: if you have some complex scenarios that need to be handled, you can create a temporary function that calls different other actions to reach a specific state, to then test out the action you are currently developing.
This enables the developer to be independent of the interface, which can become a parallel task in itself, for a second developer.
If you don’t see it yet, this is super powerful. For example, in my case, for developing a tower defense game, choosing a rendering engine can be challenging. No matter what the choice would be, the business logic of the game can be implemented right away.
Overmind lets you advance fast and test features on the way. By the time you have prototyped the entire game logic, you might as well have an idea of what engine you’d use. I like React so my options range from ReactPixi if I want a 2D game, to ReactThreeFiber for the 3D, and I even have the option to give mobile platforms a go using ReactNative.
To have a better separation between the two logics (business and interactions), I was thinking of exposing this overmind instance as game and having another overmind instance for the user-interface logic, or I could use valtio for it since it’s not that complex.
I prototyped a poor man’s game loop and tested it out in the overmind developer interface exposed in VisualStudio Code. As seen below, I have a cycle for a wave, and for each wave, a cycle that spawns the creeps for that specific level.
If you want to run the example yourself, please check the following github repository.
The only conclusion you need if you’ve gotten this far is that I’m very picky and meticulous with the tools I use on a project – and even I am considering trying it out in a real-life enterprise application scenario. The developer experience for this little experiment was so great I’m committed to put it to the test in building out reusable modules that bring predictable behaviors to state management. This has the potential of reducing the boilerplate that a normal Redux application might have.
In a typesafe environment, advanced features (like statecharts and the GraphQL addon) bring a lot to the table. So, I am excited to explore further what overmind has to offer, maybe in the next blog post/experiment.