Defining Presentation and Visualization Layer boundaries with Higher-Order Components in Vue.js 2
The purpose of this publication is to present a composition technique for working with Vue.js 2 at the level of defining architectural boundaries.
There will be no codes on this page, just in a repository that I have reserved for us to analyze calmly step by step.
It’s not about how to do it, it’s about an idea of maybe how to do it.
Summary
Introduction
What are Higher-Order Components (HoC)?
Presentation and Visualization Layer?
Why think about this architecture?
Understanding the context
Step-by-step
Presentation Layer
Accessing the UI layer
Higher-Order Component
The architectural composition
Divide and conquer
"Shortcut"
Finally.
Introduction
What are Higher-Order Components (HoC)?
Higher-Order Components extend the idea of Higher-Order Functions in functional programming. They are architectural standards described in order to share common functionality between components, excluding the code repetition.
They are functions that receive components (able to receive contexts as well) and design other components.
They were widely mentioned in the publication Mixins Considered Harmful by Dan Abramov, where they are presented as an alternative to the composition of components and a possible solution to the problematization of the use of mixins in React.
Presentation and Visualization Layer?
Although the title of the topic seems provocative to the point of trying to guess what it means, the idea behind it is a little more complex.
These concepts were mentioned in the book Clean Architecture: A Craftsman’s Guide to Software Structure and Design by Robert C. Martin (a.k.a Uncle Bob) in order to define architectural boundaries.
The image above is an illustrative representation of Uncle Bob's Clean Architecture.
Briefly, the inside circles are the most important layers of our application, centralizing the entire business rule of the system. The division from one circle to another is called the architectural boundary.
In this publication we will refer to the third and fourth layers of the figure. Visualization in context refers to the UI, which is found in the most peripheral layer of the architecture.
Why think about this architecture?
How many of us had to perform refactorings (not in the Conventional Commit sense of the word) in some of our business rules, when the UI manager changes something in the interface of the application we work on?
For this and other reasons mentioned in Bob's book, we dissociate things that change often from those who don't change as often. We separate things that change for different reasons (after all, the principles of SOLID are not only in the smallest granularity of our app, right).
Understanding the context
We will apply the examples in a proto chat app (a.k.a Hello World today).
Before proceeding, we must announce a few rules from the famous principles of Clean Architecture and Dependency Inversion.
- Our supposed Presentation Layer contains the business rule of the application, as the focus of the application is not to present how to fully implement the Clean Architecture in the project. Suppose we have other internal layers containing our domain rules, which are being imported into Presentation and being adapted for use.
- If we observe, the flow of architectural dependencies points in the right direction, considering that the Visualization Layer is from a lower level in the optics of the Presentation Layer. However, we do not see the Dependency Inversion (DIP) principle taking place. This is for the same reason as the topic above. Even though it is not the objetive, I would like to make it clear that a project in real life would need all these other peculiarities.
Recognizing these rules before the context, the outside view of what we will follow:
Our module @presentation/ChatConversation contains the intermediate of what is important in out application — business rule.
The HoC (Higher-Order Component) is responsible for composing our "dumb" components, in a way that we gain both from decoupling and from reusing smart code.
In the most peripheral we have the components themselves. The box @visualization/Component contains the UI.
It is important to note that in this model we use the script tag of our UI components only as adapters or consumers of content from the higher level layers. Fantastic!
Last but not least, we have the infamous App.vue as the orchestrator of everything. Yes, I know, I am putting the assembly of the entire application in the entry point component of the project… However, for our example we do not need a more organized structure.
I emphasize again, in a project in real life we would not do what the paragraph above exposes.
Before we start, the source of the publication can be found in this repository:
Step-by-step
Presentation Layer
Inside the presentation folder (which is in src), there is a file called chat-conversation.js.
If we look, we will have a supposed and abstract “presentation” for the conversation area in the chat. Within this module, we can display all messages or save a message in our message bucket. This is what this component must do.
Although our supposed “business rule” might be extremely simple in the given example, we gain by uncoupling things that change for different reasons — important things from not so important ones.
Accessing the UI layer
We need an interface to view the implementation of this specific business rule. Behold Visualization Layer!
For this simple example, I opted for a component that encapsulates the messages that are transiting in the chat in a tag section, in addition to another responsible for capturing the message that the user types.
Oh, and not less important, an orchestrating component will execute in an extremely declarative way, called methods coming from our presentation layer. This is to your liking.
We can see this set of features inside the components folder: the “send” and “messages” components.
If you look at the aforementioned structures according to the order in which I placed them, it may be that you arouse some doubt about where the prop receiveData comes from, the messages component.
Then we enter one of the core of this publication.
Higher-Order Component
We can see the source used for this in the shared folder inside components. I decided to distribute it within this limit just to show how useful it is in the example.
Looking at the file, we see that we have a direct dependency on the layer above in our architecture, as indicated in the diagram.
We also have a function that receives some parameters that will compose our component — the operation container.
In summary, we received the component that will be composed itself, an arbitrary name and a property called fetch. This is responsible for opening a loophole to choose the information of our presentation that we will dedicate to the component — if it is not clear, wait a little while and I will explain the other side of the coin.
We return a Vue component. This component is assembled by some information that we pass in the function signature.
Notice the dependency rule that I create between HoC and the composite component, by passing as props the information that was initially assigned to HoC by the data property. This, like the event of sending a message that I trigger to the orchestrator mentioned above, is to your liking as well. Just to continue the logic of dependency, nothing so relevant.
Something important for the functioning of this model where I declare the orchestrator declaratively to access Presentation Layer properties by event, is on line 11.
The architectural composition
We started by allowing the distribution of our components in the index.js file, contained in each folder that contains the Vue component code.
If we look at src/components/messages/messages/index.js for example, we can see that I assemble the component that prints the messages on screen.
In the fetch property (the other side of the coin) I only return what I really need from my presentation layer, reducing dependencies that are not needed — we just depend on the code we need.
In this case, for example, I “isolate” the property exibeAllMessages. This property is a getter function responsible for returning messages from our messagesList bucket, encapsulated at the file level.
At this point of decoupling, we left room to apply a design pattern that helps us with the testability of the code, called Humble Object. We'll talk about this in another episode.
Some of you may be wondering why I imported the chat presentation file into the HoC source. I just decided on the “unnecessary code dedup” perspective; HoC is contained within shared, which in turn is found within this component visualization composition. Perhaps this is the biggest reference for this dependency.
Divide and conquer
Taking a step back, a deep breath and a look at the project diagram again, we can draw some conclusions.
It seems to me that most of the principles that great masters leave to us, turn to the scope of the smallest granularities. Divide and conquer.
Today we talk about microservices, Single Responsibility Principle (SRP), Bounded Context (DDD), Composition (FP) among countless other concepts and principles that teach us how to divide a big problem into smaller ones. After all, when we have only one thing to do, the most amazing ideas — both intelligent and non-intelligent ones — are recorded. Divide and conquer.
This composition model is no different. We win by decoupling, setting limits and reusing smart code.
That last expression, reuse of smart code, a big trap that those who asked us for such technique, did not inform us.
Robert C. Martin quotes about the accidental code repetition. I believe that, following the principles of techniques such as SOLID for OOP, Composition and Currying for FP, among many others, we managed to transform our problem into a minor one. In doing so, perhaps that smell of code that can be duped is within reach of our hands.
“Shortcut”
As a professor whom I had a class in college said, “after learning the bulk, let’s take the shortcut”.
The above dependency facilitates the creation of Higher-Order Components in Vue.js.
The initial gain would be that there is no need to completely architect how will the HoC serve the limits of the components that is being shared by.
However, until now we already know the theory and practice, and can then remove an unnecessary dependency from our project.
Finally.
In this model, we have gained in decoupling at the architectural level (despite the example of the publication being lean).
We opened a margin for a higher coverage rate in our tests, gaining with the option of testing and validating our business rule in isolation.
Now, whenever the designer changes the UI of the project, our business rule is isolated from these actions, removing, once again, things that change for different reasons.
References
- Clean Architecture: A Craftsman’s Guide to Software Structure and Design de Robert C. Martin
- Mixins Considered Harmful — React Blog (reactjs.org)
- The Clean Architecture — Clean Coder Blog