Tips for back & frontend concurrent development
You wake up at 8AM and compile the app to resume your work. Suddenly the infamous runtime error appears on screen: Can´t access property X of undefined. Yesterday the feature was stable, and you didn´t pull anything from gitlab. So… you figure out this must be something related to an API change.
Did this ever happen to you? How did it affect your app? How long did it took to track and fix the issue…?
In this first of a series of articles, I will try to focus on one simple thing: API mappers. Let´s try to figure out how to optimize our frontend resources to reduce the impact of unexpected API changes.
The battle of Thermopylae: Creating a fort in the app boundaries 🧠
Themistocles, therefore, suggested a second strategy to the Greeks: the route to southern Greece (Boeotia, Attica, and the Peloponnesus) would require Xerxes’ army to travel through the very narrow pass of Thermopylae, which could easily be blocked by the Greek hoplites, despite the overwhelming numbers of Persians. (Battle of Thermopylae, wikipedia)
Let´s suppose the number of changes represents the Persian units, while your team represents the Greeks. Your app is consuming 6 API with 15 endpoints each, and the data is consumed directly among 50 components and multiple modules. That´s at least 4.500 possible weak points in the app relying on external dependencies due to a poor design.
Would you rather prefer to invest time searching the impact along with several app components, services, and templates, or rather have centralized the change in one ‘entry point’ (a.k.a. The Thermopylae pass)?
Regardless if you are using classic services with behavior subjects or a more sophisticated store system like REDUX, my advice is:
1. Keep the data-access (API Service) split in its own service. Return the raw HTTP response.
2. Call it from another feature service, and use a map pipe to transform the data to app format before storing the data locally.
If you are using REDUX, the effects service is a great place to do this operation, right before sending the new data to the reducer which leads to a new app state.
3. Last, but not least, remember NOT to call the API service directly in the components.
In terms of folder-structure, I personally like to create an API-mappers folder per module and place one function per file, which also includes the app interface. Just make sure to use typescript interfaces for the input (API model) & return the app interface, so you get all the IDE helpers.
Keep it simple: Create API to App mapper functions 🧩
While ‘API first’ is most likely to be the most effective approach to develop a project, your team doesn´t always have a choice. And here is where things can get dirty. The ability to design flexible systems, reduce the impact of the changes, and adapt fast is the key to success.
Defining independent app models, and creating API mapper functions to transform the information you gather from external dependencies, may help you in a few ways:
- Centralization of change
Avoid spreading a changing interface at multiple app layers! By placing mappers in strategic points and creating unique app models, the time of debugging and maintenance in every change will be reduced drastically. Do it in one place, and be consistent.
- Potential for changing or adding new API providers in the future
By creating unique app models, you keep the system flexible to provider changes without the need of changing existing component behavior. All you need is a single mapper.
- Style & models consistency
You may have data from several API, that follow different conventions, styles, and languages. Fruit salad is fine and healthy, but not when we are talking about large-scale application designs. Does your team have some sort of standard or preference?
- The bonsai: Reduce data overload
Quite often we get API responses with data the app doesn´t require at all, so… why should you keep it in memory? Sounds like the perfect scenario for the YAGNI principle. And as coding is concerned, the largest the item, the more distractors you got, the less effective you are.
So keep it simple. Take only what you need, and translate it into your app interface. You can come back to the map function later if your app needs to use more data.
- Encapsulation: The underlying principle of any good pattern.
“Identify the aspects of your application that vary, and separate them from what stays the same” (from the book: Head First: Design patterns).
Optimize your team reaction time against unexpected changes
Business rules may be stable, but developers understanding is not.
It´s said that business rules are likely not going to change. But developer's perception and understanding of those rules certainly do.
It follows the same physical rules as acquiring any other knowledge, demonstrated in the curve of the learning process. Even the best coders require time to see the big picture and can misunderstand the business requirements. This will translate into the natural process of mistake-learn-improve, which will lead to change. Accept it, and be ready!
Like error control: The sooner the better ⏰
Consider there is a ticking bomb hidden somewhere. Someday, somehow, something will change. And while there may not be an obvious need to implement API mappers at the beginning of a project, there is an incremental payoff with very little investment. In my opinion, the should become a must in scenarios where several teams from back and front are working at the same time frame.
This can also be applied for creating an agreement between API and frontend teams of building a good error-control system, that helps both developers and end-users. This also relates to the developer's responsibility for creating tests for the app´s core features, as quality insurance for the product your team is developing.
Did you find the article interesting?
What is your opinion?
Thanks for reading! 🙂