Defining our 2023 architecture regarding frontend React applications – part #1

Let's say you are running along the road and it happens that you find a big blocker(a boulder) so you can not move forward unless you move that big rock, and your first reaction is to just push it really hard, but it does not move a centimeter, then you realize that you will need to look for a lever so you can apply just a little bit of force, and move that boulder out of the way because you know that the right lever might amplify your output and in a similar way in Software engineering, we're looking for those levers, where we can amplify our energy, our time and produce massive gains.​
a boulder in the road

Why should we care about defining a clear architecture?

If you are the kind of developer that cares about building easier-to-scale, and easier-to-maintain applications, you might know that adding automated tests and following best practices, patterns, conventions, and guidelines would be key aspects in that respect. As this blog entry is meant to be very practical on the lessons learned, let’s dive into the recent experience I was part of a new team.

Joining a new team with a new codebase

I recently joined a new team and during my onboarding process, I started to see that there was no formal documentation about the architecture we were following for one of the code bases(a Single Page Frontend React Application)

At this point, I wanted to take some lines to emphasize the importance of working on formal documentation, and nothing better than learning from people that have a ton of experience in that matter, so let’s talk about Architecture Decision Records (ADR), and for that let me copy and paste a brief description of them.

An Architecture Decision Record (ADR) is a document that captures a decision, including the context of how the decision was made and the consequences of adopting the decision. At Spotify, a handful of teams use ADRs to document their decisions. One of these teams, The Creator Team, focuses on providing tools for creators to express themselves on Spotify, as well as access data about their content. The Creator Team utilizes ADRs to document decisions made related to system design and engineering best practices. We typically arrive at these decisions through discussion in Request for Comments (RFCs) or during our engineering meetings.

If you’re interested in reading more about that, feel free to use the button below

Coming back to the project

So, sadly, I started to see some code smells(for instance: a big React component with more than 350 lines of code) and a bit of complexity during a few code reviews, even though the React Application was relatively new(created 5 months ago).

What I respect from other developers when making corrections​​

One of the things that I respect from other developers is the ability to take decisions after they realized that the current codebase quality is kind of poor and very complex to understand/maintain and that is when you need to decide and ponder between:

And in other words, it is very important to take into consideration all the effort and budget already spent on this project, but at the same time care about quality unless this is an MVP that we want to ship in no time and when validated we can deprecate that version and start with a brand new version but in this second version we would be following best practices and the right conventions in that specific language or technology so it can last for years without major refactoring.

The project details - use case example

Let’s look into some of the project details so you can agree or disagree with some of the reasoning behind some of the decisions we took and you have a better understanding of the application.

What kind of project are we talking about? In a few words, it is a Single Page Application portal, tightly integrated with an external data source(Microsoft Dynamics 365 https://dynamics.microsoft.com/en-us/), and that we utilized to manage different entities by making CRUD requests on demand.

In addition to using the CRM as the main data source, we also support some additional third-party services and we GET and POST data to them(for instance: asana.com)

Probably, a similar product that is public and in fact, we utilized as a part of our Agile management process is a service called ZenHub.com

More details on the REact 18 application

Some additional details would be that this application uses Single Sign On through a custom SSO provider(similar to Google SSO or Github.com SSO), and in order to make some validations in the backend, we built a Ruby on Rails Application that handles authentication, generates tokens and serves as a layer of protection for the CRM and any other third-party connections, so only the React application knows the basics to interact with those external services.

The tech debt problem

In the beginning, I said that the architecture of this React application was not clear, we were not following any formal conventions and patterns for doing similar implementations and functionality, sometimes we were not re-using code that was common between sections, and we were not using properly some React libraries/dependencies, and at the end, we were breaking some basic development principles and that we normally follow when we use a very well know framework such as Ruby on Rails(convention over configuration).

In addition to that, there were no integration/E2E/system tests - We are going to review our options in one of the parts of this series and we are going to review the importance of testing modern Single Page Applications(SPA) so we can get the opportunity to test our application logic at the component level as well as the complete app as a black box.

Component-based architecture tech debt(React)

Talking about React and probably other frameworks, it really seems like there is a trend to stick a bunch of app logic in the views (components) these days with the adoption of Hooks and functional components. It seems really simple and trivial at first, but as the app grows or you need to refactor, it can get very messy and difficult to maintain.

This sentence reflects very well, the current state for some sections in the React Application use case, and one of the reasons that led to this end result was partly because we didn't have a clear definition of our architecture since the beginning(Any new engineer was following their own implementation style).

But heriberto please share more specific examples in the technical debt respect.​​

Sure, let’s begin with a few of them.

1. A big component with more than 350 lines.

Unfortunately, I can not share with you the current file, but let me describe it for you.

If you would open that file in your editor you would see:​

2. Not using the React Router properly

Similar code smell associated with the same big component, as we forgot to create new routes we started adding everything on top of the same route and same big component, and the end result was that if you wanted to access a direct option, that was not possible and in the case that you needed to reload the page, you would need to start from scratch selecting the blog entry and then clicking on some of the options and finally landing to the content/option you wanted to see or click.

Also, the name of that single route was looking strange and funny in the form of:

3. Using React Redux for everything

React Redux( we will talk about it in detail later on), but sometimes even for a very small state change or for passing simple properties, we were using and adding new reducers for everything, without caring about the performance, and in other words, as we would talk about the state management later starting as simple as possible, by using local state and passing down properties then when needed and getting complex then start considering the use of Redux and React Context only if, the data involved needs to be accessed by different levels of nested components and we want to prevent props drilling, but not before that.

4. HTTP requests everywhere

We were dispatching actions and using fetch everywhere, sometimes, directly in the components, sometimes, in the actions, services, utils, and everywhere, without a clear organization, one of the problems we found, later on, was when let’s say the session would be expired and then we would need to add a catch on all those "fetches" to listen for specific status code such as 401 when the session has expired and the user is not able to submit information or click on an option.

I think that should be enough for you to get a sense of the application and the kind of improvements, we had the opportunity to achieve here.

About this blog series

After that long introduction and context, in this series of blog entries, we are going to start with some concepts and aspects regarding Single Page Applications that use components, we are going to review how we can structure them, if we are using React we would compare some of the dependencies we can use, including:

Let’s begin by analyzing the first items on our list, regarding

What a clear architecture might look like

for our React Application if it were 2023.

1). Analyzing what you are about to build - let's start with the "What"

At this point, you already know and answered the WHY...

Why are you building y, x, or z? And as that was a very conscious answer and the right approach, the next step is about the WHAT(details)

Deciding on how to divide your application functionality is an important aspect of the initial analysis, so we can see how every piece would interact with the other(s) and at that time we can decide on the kind of state(local or global) that we will need to satisfy in the short term. That will allow you to deliver a great browser experience, and create independent and reusable components of the App.

Dividing and organizing your application functionality in the right way will help you improve the simplicity of the building process, for this specific point, I would work on some diagrams based on the first features we want to build, let’s say it is about

And in general, it is about the big picture of the organization and then being able to translate that into components and components that contain other components and if you are using the React Router you can start identifying individual routes.

2). Props drilling

Dealing with state management in React applications can be a tricky thing, especially when data needs to be passed from a root component down to deeply-nested components. We, as React developers, often tend to over-engineer our applications by relying too heavily on the Context API and Redux in situations where they aren’t needed. We reach out too quickly for these tools — even in basic situations that simply require passing state/data to deeply-nested components or by using composition — all in an attempt to overcome prop drilling.

What is prop drilling?

Prop drilling is the unofficial term for passing data through several nested children components, in a bid to deliver this data to a deeply-nested component. The problem with this approach is that most of the components through which this data is passed have no actual need for this data. They are simply used as mediums for transporting this data to its destination component.

This is a typical case of prop drilling. This is where developers often resort to the Context API, Redux, and Composition patterns as a means of bypassing this supposed problem, without giving much thought to the potential problems created therein.

3). Routing and routes

Single Page Applications (SPA) — web applications that are located on a single web page (HTML file), but DOM manipulation (and often AJAX requests) to produce the appearance of multiple “web pages”. This structure is facilitated by the use of the client-side routing library react-router, which allows you to render different Components based on the browser’s URL, allowing each View (“page”) to be treated as a unique and individual resource and so to have its own URI, thus allowing each View to be referenced individually.

Client-side routing allows you to have unique URLs for each View, but will also make the app work faster—instead of needing to download an entire brand new page from the server, you only need to download the requisite extra data (e.g., using an AJAX request), with much of the other content (the HTML, CSS, etc) already being in place. Moreover, this will all your app to easily share both state data and particular components (e.g., headers, navigation, etc).

Google Drive is a good example of a Single-Page Application. Notice how if you navigate to a new folder, the URL changes (so you can link to individual folders), but only a single “pane” of the page changes.

As we are building a Single Page Application that is a reader/manager coming from an external API with external data(CRM, Asana, and other third-party services), zenhub.com would be a great reference for us, as the Single Source of Truth for them is Github.com and in our case, it is sometimes the CRM and sometimes a different third party service.

One quick example we can see is how they show the details of an issue in a popup, for example, if you visit this URL directly:

You can access that directly, but if you open that link in the same dashboard is going to show you the same information but as a modal popup, we can get a few great reference on the way that it works and the UX flows they have.

As we can learn a lot from existing examples, let’s look into a more specific one that we can migrate to in the future.

The hypothetical section on our React application would be the “Blog Entries” one and the “BlogEntryPage” component, as we might realize a few improvements we can make here.

We were using the same route, even though we were allowing the user to select different blog entries and showing associated data/sections to that blog entry(eg: comments, likes, etc) but the User eXperience was very poor as if you needed to reload the page then would encounter that the previous selection(s) was(were) lost and you were forced to start again that flow or not able to share direct URLs to specific blog entries.

That route looked like this:

Don't you think that URL name looks a bit funny as it is using the _ as part of the name, I have seen far more usage of hyphens vs underscores, Haven’t you? so that would look like this

But as we were showing one or more “entries” and the user was able to select and show details about that specific selection I think it would make more sense to use a name in plural, so probably a better name would be:

Ok, ok, lesson learned.

Another side effect of not using routes properly that you might encounter

Several

useEffect(() => {}, []);

Hooks listening for different changes in the objects associated.

There is nothing wrong if you want to use multiple useEffects in your components but in our case, some of them do not belong to that component, like showing comments every time you switch between blog entries in order to see associated information

How can we solve that? By using React Router in the right way, or in other words, a route that will show the details for the selected blog entry and any other associated information(eg: comments), in the form of:

What can we learn from this? The next time we could do more analysis before jumping into code and Analyzing what we are about to build would help us to decide on the best way to proceed and definitely take into consideration independent and easy-to-access routes and follow best practices for that.

4). Small components and the Single Responsibility Principle

Splitting into small chunks of a big component is a good practice, and being aware whenever a component is doing a lot of stuff, responsibilities such as retrieving data from the backend, iterating through the elements, and printing all details for individual elements, so in those cases, we need to identify if we are violating any software development principle(Single Responsibility one).

To be continued…

I know that at this point we haven’t got our hands dirty by sharing any code, but don’t worry eventually we are going to come to that point where I’ll be sharing real code examples.

In the case that you got interested, you can keep reading the second part

No Comments

Post A Comment