DEV Community

Cover image for Clean Architecture for the Front-End
Nicolas
Nicolas

Posted on

Clean Architecture for the Front-End

局 Clean Architecture: Revolutionizing the Front-End

Description: Discover how Clean Architecture can transform and optimize front-end development.

Introduction:

In an increasingly complex computing world, structuring your code effectively is crucial. It relies on the clear separation of an application's components, facilitating testing and reducing dependence on external tools. In this article, we will explore how Clean Architecture can transform your projects by examining its principles, structure, benefits, and its application to a front-end project.


What is Clean Architecture?

Conceived by Robert C. Martin, this software design philosophy places the business concerns of the application at the center of attention. It organizes the code into concentric layers, each with a specific and independent role.

Clean architecture schema
Source : https://miro.medium.com/

Clean Architecture - Divide and Conquer

The primary goal of Clean Architecture is the separation of code into different layers, each with its own responsibility.

Here are the elements that make up this methodical structure:

Entities: Also called the Domain, these are the central elements representing the business logic and rules of the application, independent and can be used by all layers of the application.

Use Cases: They encapsulate all the specific business logic of the application and orchestrate the flow of data between the entities and the outer layers.

Interface Adapters: Also called controllers, presenters, or gateways, they convert data between the formats appropriate for the use cases and entities.

User Interfaces (UIs) and Frameworks & Drivers: They manage interactions with external elements like user interfaces, databases, and external systems.


Adapting to the Front-End

By adapting Clean Architecture to front-end development, we clarify the structure of the code. Entities define data models and business rules, use cases manage user interactions, interface adapters take care of the connection between the UI and the data, and the outer layer interacts with frameworks and external services. This well-defined organization facilitates the maintenance and evolution of front-end applications. Next, let's discover how this approach brings significant benefits to front-end development.

Clean Architecture on the Front-End

The Benefits

The benefits of Clean Architecture for the front-end are similar or identical to those for a back-end application.

  • Framework Independence: Frameworks are then used as tools rather than the other way around.
  • UI Independence: The architecture does not depend on the user interface.
  • Database Independence: It remains unattached to any specific database.
  • Granularity of Tests: It is easy to precisely target a layer/component for testing.

Thanks to this flexibility and increased maintainability, Clean Architecture is a very good choice for large-scale front-end applications.

Implementing this type of architecture indeed incurs a higher cost at the beginning of the project, but the advantages of the method allow for quickly saving time in the future.

Preparation for Implementation

On the back-end side, business rules are fairly straightforward and often intuitive, representing the product's functionalities (creating an account, booking a book, making a payment, etc.). On the front-end side, it's not as evident because the domain (Entities) consists of display rules and the rendering behavior by a browser. Moreover, nothing is defined or conventionalized for the application of Clean Architecture on the Frontend: everything is modular and adaptable according to needs. A small application can very well use only two layers, such as the entities and the outermost layer.

Let's take an example with a task management app. A user has expressed a need, which is translated by our Product Owner into a set of rules that define the steps when the user uses the application.

1. Task Selection: When a user clicks on a task to select it:

  • If the task is already selected, it should be deselected.
  • Otherwise, it is marked as selected.

2. Task Categorization:

  • If a task is categorized as "to do immediately," it moves to the top of the list.
  • If the task is marked "to do later," it is placed in a separate section.

3. Sub-task Management:

  • If a main task is selected, all its associated sub-tasks appear.
  • The user can select or deselect individual sub-tasks.
  • If a main task is deselected, all its sub-tasks are also deselected.

If you're observant, you can already discern a simple description of an algorithm taking shape. That is the domain.


In Practice

Let's revisit a simple task management application.

Here is the structure of our application:

Image description

If we apply the Clean Architecture model, here is what it would look like:

Clean architecture model for Front End

Let's proceed step by step, starting from the center of our architecture by defining our entity Task.ts

interface TaskData {
  id: string;
  title: string;
  description: string;
  completed?: boolean;
}

class Task {
  id: string;
  title: string;
  description: string;
  completed: boolean;

  constructor({ id, title, description, completed = false }: TaskData) {
    this.id = id;
    this.title = title;
    this.description = description;
    this.completed = completed;
  }
}

Enter fullscreen mode Exit fullscreen mode

Next, let's add an adapter that allows us to use our entity. This adapter is very similar to services in a more conventional architecture.

import axios from "axios";
import Task from "../entities/Task";

const TaskService = {
  async fetchTasks() {
    try {
      const response = await axios.get("https://your-api-endpoint.com/tasks");
      return response.data.map((taskData: Task) => new Task(taskData));
    } catch (error) {
      console.error("Error fetching tasks:", error);
      throw error;
    }
  },
  async addTask(taskData) {
    try {
      const response = await axios.post('https://your-api-endpoint.com/tasks', taskData);
      return new Task(response.data);
    } catch (error) {
      console.error('Error adding task:', error);
      throw error;
    }
  },
};

export default TaskService;

Enter fullscreen mode Exit fullscreen mode

Between these two elements, we also need a use case that establishes the link between the entity and the adapter.

// ManageTasks.ts
import Task from '../entities/Task';
import TaskService from '../adapters/TaskService';

class ManageTasks {
  async addTask(taskData: Task): Promise<Task> {
    try {
      return await TaskService.addTask(taskData);
    } catch (error) {
      console.error('Error adding task:', error);
      throw error;
    }
  }

  async fetchAllTasks(): Promise<Task[]> {
    try {
      return await TaskService.fetchAllTasks();
    } catch (error) {
      console.error('Error fetching tasks:', error);
      throw error;
    }
  }
}

export default new ManageTasks();
Enter fullscreen mode Exit fullscreen mode

This allows us, finally, to add this logic into the Task List component, which acts as an adapter facilitating the connection between our internal layers and the Vue framework.

<template>
  <div>
    <ul>
      <li v-for="task in tasks" :key="task.id">
        {{ task.title }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";
import ManageTasks from "@/use_cases/ManageTasks";
import Task from "@/entities/Task";

onMounted(() => {
  loadTasks();
});

const tasks = ref<Task[]>([]);

async function loadTasks() {
  try {
    tasks.value = await ManageTasks.fetchAllTasks();
  } catch (error) {
    console.error("Failed to load tasks:", error);
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Conclusion

The use of Clean Architecture in the front-end, as demonstrated in this example, highlights its numerous advantages.

  1. Framework-Independent Layers:
  • Entities: This core layer of the architecture contains business logic and data models. It is completely independent of Vue.js or any other framework, meaning the code here can be reused regardless of the front-end framework.

  • Use Cases: This layer manages application-specific logic and is also independent of Vue.js. It processes data and interactions without concern for how data is presented or how user inputs are received.

  1. Interface Adapters:

    This layer acts as a bridge between the framework-independent layers and Vue.js-specific layers. Here, we find components that link the external and internal layers. By isolating Vue.js-specific interactions in this layer, the rest of the application remains independent of the framework.

  2. Frameworks & Drivers:

    The outermost layer interacts directly with Vue.js. This includes Vue components and views, directives, filters, and other framework specifics. By concentrating all Vue.js-specific interactions in this outer layer, you reduce your business logic and use cases' dependency on the framework. This makes your application more flexible and easier to test, maintain, or even migrate to another framework if necessary.

By following this structure, Clean Architecture enables the construction of front-end applications where the choice of framework (in this case, Vue.js) does not influence the business logic or the application's rules, offering greater flexibility and improved testability.

So, are you convinced, ready to take the step in your next front-end projects?

Top comments (0)