Clean architecture for the front-end



The modern web is complicated. The number of frameworks and the pace of their development makes the developer gallop. Someone uses new ones, someone reads fashion books. But sometimes reading and energy spent deepening in architecture, OOP, TDD, DDD, etc. do not live up to expectations. And sometimes the books are confusing! And even, the worst thing, they incredibly raise the FAC!

I will venture to set out in a simple way the basic idea of ​​Pure Architecture as applied to the front-end. I hope this will be useful for people who want to read this book, and for those who have already read, but do not use the knowledge gained in real life. And for those who are interested in how I dragged the front end here.

Motivation


The first time I read the FAQ is about a year and a half before writing an article on the advice of one of the senior developers. Before that, I was very impressed with the short excerpts from Pure Code adapted for JavaScript ( https://github.com/ryanmcdermott/clean-code-javascript ). I kept this tab open for six months to apply the best practices in my work. Moreover, my first attempts to read the original Clean Code failed. Perhaps because it is too adhered to the features of front-end projects, or because reading should be reinforced by long-term practice. Nevertheless, the Cheka is a practical guide that you can take and immediately apply to the written function (I strongly recommend that you read it first if you are not familiar).

But with TA, it’s more and more complicated - here are sets of principles, laws and tips for building the program as a whole. There is no specifics that you can immediately take and zayuzat in the component. This book is designed to change the way you write software, and give you mental tools and metrics. It is a mistake to consider that the ChA is useful only to architects, and I will try to convey this using an example adapted to the front-end.

Business logic and frontend




When it comes to ChA, many people draw circles in their imagination (see the figure under the heading), gigantic-sized projects, incredibly complex business logic, and a bunch of questions - what kind of daddy to put YuzKeysy? The idea of ​​the applicability of the principles of ChA to the creation of an accordion component is perplexing. But the problems that arise when developing modern interfaces require a serious attitude. Modern interfaces are complex and often painful. First of all, let's figure out what is most important on the front-end.

Everyone has long known that you need to separate business logic from a presentation, and observe SOLID principles. Uncle Bob in the CHA will tell you about this in great detail. But what is business logic? R. Martin offers several definitions and subcategories; one of them sounds approximately So:
Business logic (business rules) are rules that bring money to organizations even without automation.
In general, business logic is something very important. (I hear the backenders giggle when they hear about business logic from the fronts). But I suggest that the front-runners relax a bit and remember what can be very important at our front? And no matter how strange it sounds, the most important thing on the front is the user interface. The first step to understanding the AA for me was the realization that the interface logic should be in the center in the front circle! (fig. under the heading).

I understand that this is an unfounded statement, and one can argue strongly with it. But let’s remember that what is changing at the front often and painfully? I don’t know about you, but I am most often asked to change the behavior of interactive elements - accordions, molds, buttons. It is worth noting that layout (design) changes much less frequently than interface behavior. Changes and actors ( initiators of changes ) are especially discussed in the PA , note.

Nasty mold


Let's not bother much for terminology. We give an example and clarify a little what I call the interface logic.

There are a couple of inputs with validation and conditional mapping. You can fill in only one field, and the form is valid, but optional inputs will appear. (Pay attention, we don’t care what kind of data is there. The main thing is interactivity)





I hope the logic is clear. And at first, the implementation does not raise questions. You shove this thing into a component of your framework and boldly reuse it in other forms as an integral part and an independent one. Somewhere you have to pass flags in order to slightly change the validation, somewhere a little layout, somewhere the features of connecting to the parent form. In general, code on thin ice. And once, you get the task in this form (and using it) and freeze for a couple of days, although it would seem to be 15 minutes. Damn the manager, molds and boring dull tasks.

Where is the mistake? It would seem that you have been working for more than a day, thought out the composition of the components very well, tried different hockeys, transmitted templates through props, conjured with the framework for building forms, even tried to follow SOLID when writing these components.

Note: components in ChA! == components in react / angular and co.

But the fact is that you forgot to highlight the logic. Calm down a bit, go back to the task and play simulation.

Too simple task


The NA emphasizes that architecture is critical for large projects. But this does not negate the usefulness of the ChA approaches to small tasks. It seems to us that the task is too simple to talk about the architecture of some form. And how to designate a way to make changes, if not through architecture? If you do not define the boundaries and components of the interactive interface, it will be even easier to get confused. Remember with what thoughts you take the task of changing the form at your work.

But let's get down to business. What is this form? We are trying to model, expressing thoughts with pseudo-code.

ContactFormGroup
  +getValue()
  +isValid()

In my opinion, our whole task for the outside world is to create an object with two methods. It sounds easy - the way it is. We continue to describe what we see and what interests us.

ContactFormGroup
  emailFormGroup
  phoneFormGroup
  getValue()
    => [emailFormGroup.getValue(), phoneFormGroup.getValue()]
  isValid()
    => emailFormGroup.isValid() || phoneFormGroup.isValid()

Probably, it is worth explicitly indicating the appearance of minor inputs. When a manager asks you to quickly make the 10th edit to the form, then everything looks simple in his head - just like this pseudo-code.

EmailFormGroup
  getValue()
  isValid()
  isSecondaryEmailVisible()
    => isValid() && !!getValue()

Can we spot a place for strange demands ...

PhoneFormGroup
  getValue()
  isValid()
  isSecondaryPhoneVisible()
    => isValid() && today !== ‘sunday’

One of the implementations of our form on Angular might look like this.

ContactFormGroup implementation (Angular)
export class ContactFormGroup {
    emailFormGroup = new EmailFormGroup();
    phoneFormGroup = new PhoneFormGroup();

    changes: Observable<unknown> = merge(this.emailFormGroup.changes, this.phoneFormGroup.changes);

    constructor() {}

    isValid(): boolean {
        return this.emailFormGroup.isValid() || this.phoneFormGroup.isValid();
    }

    getValue() {
        return {
            emails: this.emailFormGroup.getValue(),
            phones: this.phoneFormGroup.getValue(),
        };
    }
}

export class EmailFormGroup {
    emailControl = new FormControl();
    secondaryEmailControl = new FormControl();

    changes: Observable<unknown> = merge(
        this.emailControl.valueChanges,
        this.secondaryEmailControl.valueChanges,
    );

    isValid(): boolean {
        return this.emailControl.valid && !!this.emailControl.value;
    }

    getValue() {
        return {
            primary: this.emailControl.value,
            secondary: this.secondaryEmailControl.value,
        };
    }

    isSecondaryEmailVisible(): boolean {
        return this.isValid();
    }
}


Thus, we get three interfaces (or classes, not important). You should put these classes in a separate file in a prominent place so that you can understand the moving parts of the interface by simply looking into it. We have identified, pulled and emphasized the problematic logic, and now we control the behavior of the form, combining the implementation of individual parts of ContactFormGroup. And the requirements for different use cases can be easily represented as separate objects.

This seems to be a standard implementation of the MVC pattern, and nothing more. But I would not dismiss elementary things that in practice are not respected at all. The point is not that we pulled a piece of code from the view. The point is that we have highlighted the important part that is subject to change and described its behavior so that it becomes simple.

Total


ChA tells us about the laws of writing software. It gives metrics by which we can distinguish important parts from minor ones and correctly direct dependencies between these parts. Describes the benefits of OOP and approaches to solving problems through modeling.

If you want to improve the quality of your programs, make them flexible, use OOP in your work, learn how to manage dependencies in your project, talk in the code about the solution to the problem, and not about the details of your library, then I highly recommend reading Clean Architecture. The tips and principles from this book are relevant for any stack and paradigm. Do not be afraid of experiments on your tasks. Good luck

PS About state management


A very big obstacle to understanding NA can be a commitment to a state management library. In fact, libraries such as redux / mobx simultaneously solve the task of notifying components about changes. And for some developers, a front without a state manager is something unthinkable. I believe that the principles of ChA can be applied with and without the use of a state manager. But by focusing on the state management library, you will inevitably lose some of the flexibility. If you think in terms of ChA and OOP, then the concept of state management generally disappears. The simplest implementation without state management is here habr.com/en/post/491684

PPS


Honestly, I showed the solution to a similar interface task to my friend - he did not appreciate it, and rewrote everything for reaction hooks. It seems to me that to a greater extent rejection is due to the fact that OOP is practically not used in real projects, and most of the young front-end developers have not the slightest experience with OOP solutions. At interviews, they sometimes ask about SOLID, but often only to weed out candidates. Moreover, the gusts for development in the field of PLO in some teams can be stopped by a review. And it is often easier for people to sort out a new library than to read a boring book or to defend their right to take logic out of a component. Please support OOP activists :)

All Articles