{"id":13462352,"url":"https://github.com/juanm4/hexagonal-architecture-frontend","last_synced_at":"2025-03-25T01:32:11.918Z","repository":{"id":37971662,"uuid":"338876589","full_name":"juanm4/hexagonal-architecture-frontend","owner":"juanm4","description":"How to implement Hexagonal architecture in frontend (Javascript/Typescript)","archived":false,"fork":false,"pushed_at":"2024-03-28T09:27:28.000Z","size":963,"stargazers_count":459,"open_issues_count":4,"forks_count":66,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T11:11:13.803Z","etag":null,"topics":["design-patterns","front-end","hexagonal-architecture","javascript","react-native","reactjs","solid-principles","typescript","vuejs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juanm4.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-14T18:46:57.000Z","updated_at":"2024-10-29T06:26:13.000Z","dependencies_parsed_at":"2024-01-16T05:09:02.251Z","dependency_job_id":"08130b71-a1eb-4784-a298-56513acf6506","html_url":"https://github.com/juanm4/hexagonal-architecture-frontend","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanm4%2Fhexagonal-architecture-frontend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanm4%2Fhexagonal-architecture-frontend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanm4%2Fhexagonal-architecture-frontend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanm4%2Fhexagonal-architecture-frontend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juanm4","download_url":"https://codeload.github.com/juanm4/hexagonal-architecture-frontend/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245382166,"owners_count":20606163,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-patterns","front-end","hexagonal-architecture","javascript","react-native","reactjs","solid-principles","typescript","vuejs"],"created_at":"2024-07-31T12:00:45.508Z","updated_at":"2025-03-25T01:32:11.563Z","avatar_url":"https://github.com/juanm4.png","language":"TypeScript","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# How to implement Hexagonal architecture in frontend (Javascript/Typescript)\n\nTable of contents:\n\n1. [ Introduction ](#introduction)\n2. [ Why is this architecture called hexagonal? ](#why)\n3. [ Same concept different names ](#same)\n4. [ How does it affect maintainability? ](#maintainability)\n5. [ How does it affect the frontend? ](#frontend)\n6. [ Historical context ](#historical)\n7. [ Present ](#present)\n8. [ Consequences ](#consequences)\n9. [ What we do with frontend frameworks/libraries? ](#frameworks)\n10. [ Example of Hexagonal Architecture ](#example)\n    * [ Directories structure ](#examDir)\n    * [ Domain ](#examDomain)\n    * [ Data access ](#examData)\n    * [ Views ](#examViews)\n    * [ Third party libraries ](#examThird)\n    * [ Tests ](#examTest)\n11. [ Final words ](#final)\n12. [ What does include this project? (Extra bonus) ](#bonus)\n\n\u003ca name=\"introduction\"\u003e\u003c/a\u003e\n## Introduction :wave:\nThere are multiple definitions for the term architecture, depending on the context, and the development branch you come from. For these reasons is complicated to reach a consensus and a single definition that is valid for all cases. So, according to frontend software development, and from a professional point of view, the definition could be the next:\n\n**Developers call architecture the set of development patterns that allow us to define guidelines to be followed in our software in terms of limits and restrictions. It is the guide that we must follow to order our code and make the different parts of the application communicate with each other.**\n\nThere is a wide range of options when it comes to choosing one architecture or another. Each\nwill have its own advantages and disadvantages. Even once we choose which one is the\nbest suited to our case, it does not necessarily have to be implemented in the same way in different projects.\n\nHowever, although the amount of options is almost infinite, the most keep their quality attributes in common, such as: scalability, single responsibility, low coupling, high cohesion, etc.\n\nSo, in general way, it's crucial to understand the concepts, and the reason why you have chosen one solution or the other.\n\nOne of the most used patterns to design software architecture is Hexagonal Architecture, also known as Ports and Adapters.\n\nThe purpose of this pattern is to divide our application in different layers, allowing it to evolve in an isolated way and making each entity responsible for a single functionality.\n\n\u003ca name=\"why\"\u003e\u003c/a\u003e\n## Why is this architecture called hexagonal? :thinking:\n\nThe idea of representing this architecture with a hexagon is due to the ease of associating the theoretical concept with the visual concept.\nInside this hexagon is where our base code is located. This part is called **domain**.\n\nEach side of this hexagon represent an interaction with an external service, for example: http services, db, rendering...\n\n\u003cimg src=\"./resources/hexagonal_architecture.png\" style=\"background-color: white\" alt=\"hexagon\" height=300 /\u003e\n\n\nThe communication between **domain** and the rest of actors is performed in the **infrastructure** layer. In this layer we implement a specific code for each of these technologies.\n\nOne of the most recurrent question among professionals that see this architecture for the first time is: Why a hexagon? Well, the use of a hexagon is only a theoretical representation. The number of the services we could add is infinite, and we can integrate as many as we need.\n\n\u003ca name=\"same\"\u003e\u003c/a\u003e\n## Same concept different names :unamused:\n\nHexagonal Architecture pattern is also called **Ports and Adapters**. This name come from a separation within a **infrastructure** layer, where we will have two sublayer:\n\n- **Port**: It is the interface that our code should implement in order to abstract from technology. Here we define the method signatures that will exists.\n\n\n- **Adapter**: It is the implementation of the interface itself. Here we will have our specific code to consume a concrete technology. It's important to know that this implementation should NOT be in our application, beyond the declaration, since its use will be realized through the **port**.\n\nSo, our domain will make calls to the sublayer that corresponds to the port, being decoupled from the technology, while the port, in turn, will consume the adapter.\n\nThe **Ports and Adapters** concept is very linked to object-oriented programming and to the use of interfaces, and maybe, the implementation of this pattern in functional programming could be different from the initial concept. In fact, it has arisen many patterns that iterate over this, such as **Onion architecture** or **Clean Architecture**. At the end the goal is the same: divide our application in layers, separating **domain** and **infrastructure**.\n\n\u003ca name=\"maintainability\"\u003e\u003c/a\u003e\n## How does it affect maintainability? :monocle_face:\n\nThe fact of having our code separated in layers, where each of them has a single responsibility, it helps each layer to evolve in different ways, without impacting the others.\n\nAlso, with this segmentation we get a high cohesion, where each layer will have single and unique responsibility well-defined inside the context of our software.\n\n\u003ca name=\"frontend\"\u003e\u003c/a\u003e\n## How does it affect the frontend? :open_mouth:\n\nThere are currently a number of shortcomings in the use of methodologies when creating applications. Today, we have an incredible amount of tools that allow us to develop applications very fast, and at the same time, we have put the analysis and implementation of known and proven architectures on the back burner.\n\nNotwithstanding, even though this architectures may appear to be from the past, where the languages didn't evolve so fast, these architectures has been showed and adapted to give us the scalability we need to develop actual applications.\n\n\u003ca name=\"historical\"\u003e\u003c/a\u003e\n## Historical context :sleeping:\n\nTwo decades ago desktop applications were the main tool to develop. In them, all our application was installed in the machine, through libraries, and there was a high coupling between view and behaviour.\nThen, we wanted to scale our applications to get a software more maintainable, whit centralized databases. So many of them were migrated to a server. With this, our desktop apps were reduced to a \"fool\" applications, which didn't require access, persistence or many data.\nFinally, if the app needed some data, it had the responsibility to perform this calls to the external servers through network services. It's here when we started to distinguish between \"frontend\" and \"backend\".\n\nDuring the next years we got the web boom. Many desktop apps were adapted to the browsers, where the limitations were higher with only HTML. Later, JAVASCRIPT started to give more possibilities to the browser.\n\n\u003ca name=\"present\"\u003e\u003c/a\u003e\n## Present :relieved:\n\nThe views had always been limited only to data representation and them had never needed higher functionalities, until now. With the common needs, frontend applications have more requirements than years ago. To name a few examples: state management, security, asynchrony, animations, integration with third party services...\n\nFor all of these reasons, we need to start to apply patterns on these apps.\n\n\u003ca name=\"consequences\"\u003e\u003c/a\u003e\n## Consequences :partying_face:\n\nAs we have said, the purpose of the frontend is mostly to visualize data. Despite this perception, it is NOT the **domain** of our application, but belongs to the outer layers of the implement architecture.\n\nThe use cases of the app do belong to the **domain**, and they shouldn't know how the data must be visualized.\n\nFor example, suppose you are developing a shopping cart. One use case could be: \"A shopping cart can not have more than 10 products\".\nAnother use case: \"A shopping cart can not have the same product twice or more\". We can see the use cases as requirements in our application.\n\nThe data requests to backend belong to **infrastructure** layer, and it is something that our app doesn't need to know, even if we manage the backend (it is a different applications and has different architecture needs. At some point, the data scheme of backend could change, and we don't want to our app can be affected by that.\n\nAnother part that belong to **infrastructure** is the management of local data, like session data, cookies or local databases. Of course, we have to deal with this, but it is not part of our **domain**.\n\n\u003ca name=\"frameworks\"\u003e\u003c/a\u003e\n## What we do with frontend frameworks/libraries? :sunglasses:\n\nNowadays there is a huge amount of libraries for rendering: Angular, React, Vue...; but we must understand what their purpose is, so they should not enter the **domain**, but they should be handled into the **infrastructure**.\n\nAll these tools tend to evolve quickly, and our code doesn't want to be affected by them. Now, how can we achieve that? Well, one of the most common strategies is wrap these libraries in created functionalities for this purpose. This strategy is known as \"wrapping\" and with that, we can isolate our code from the side effects that these libraries may have.\n\n\"Wrapping\" is a good practice, but when we use it, we must do it through an **adapter** to reduce the coupling. Also, this technique has disadvantages too. For example, if we abuse of that, we may be over-engineering our product, increasing maintenance time. So, we have to identify in which cases it is worthwhile and in which cases it is not.\n\nWe can affirm that communication between **view (infrastructure)** and **domain** is unidirectional, it means, they are an entry point for the user, but they will never been consumed by the **domain**.\n\nOnce this is understood, we have to assume that tools highly coupled to these frontend libraries, like Redux or Vuex, must be managed in the **infrastructure** layer.\n\n\u003ca name=\"example\"\u003e\u003c/a\u003e\n## Example of Hexagonal Architecture :rocket:\n\nNow it's the show time, let's try to put all this theory into practice through an example. Let's write some code.\n\nImagine that we have to design a shopping cart, and we have to do it in \"reactjs\", \"vuejs\" and \"React Native\".\n\nFirst, we think in which entities come into play, known that we will retrieve the data through a third party service (we will see that later).\n\n- **Product**\n- **Cart**\n\nWe also know these entities must be available for the user, so that the user can interact with them. The user could do the next:\n\n- See a list of products\n- Add products to the shopping cart\n- Remove products from the shopping cart\n\nNow imagine that we have the following business rules:\n\n- A shopping car can not have more than 5 products\n- The same product can not be in the cart twice or more\n- The maximum price of the cart must be 100 €\n\n\u003ca name=\"examDir\"\u003e\u003c/a\u003e\n### Directories structure :card_index_dividers:\n\nHere we can see an example about how organize the directories, for both the \"React\" and \"Vue\" applications.\n\n\u003cimg src=\"./resources/directories.png\" alt=\"Directories structure for hexagonal architecture\" height=auto /\u003e\n\nLet me explain a little more what represent each folder.\n\n- **domain**\n    * **models** Here we have the models we will need, both types and interfaces of each model.\n    * **repositories**  All types and interfaces related with the repositories (a repository is in charge of bringing data from a web service, or a database, or a file...).\n    * **services** A service is responsible for interacting with our models and performing actions on them. For example to get the products or add a product to the cart.\n- **infrastructure**\n    * **http** Here are stored things related with our client, in this case a http client.\n        + **dto** All dto's that we receive from a repository.\n    * **instances** He we have created concrete instances for our client and repositories. You can see like the entry point of your system. Maybe this is not the best place for this folder, we have created in this way to use fake data since we do not have a web service.\n    * **repositories** Here we are defined the repositories we need to get products.\n    * **views** This folder store all related with our views. \n        + **react-ui** React project that interact with our models and services.\n        + **reactnative-ui** React Native project that interact with our models and services.\n        + **vue-ui** Vue project that interact with our models and services.\n- **mocks** Here we have mock data that our client will use to provide concrete dto's with.\n- **test** All unit test for the use cases.\n\nWe have created two directories: domain and infrastructure. All visual components are allocated inside infrastructure (remember that views and representations don't belong to our domain).\n\n\u003ca name=\"examDomain\"\u003e\u003c/a\u003e\n### Domain :shield:\n\nNow we are going to define the domain's models (Product and Cart) with the respective interfaces required.\n\n```ts\n// src/domain/models/Product.ts\n\nexport type Product = {\n    id: string;\n    title: string;\n    price: number;\n};\n```\n\n```ts\n// src/domain/models/Carts.ts\n\nimport { Product } from './Product';\n\n// This interface define what operations we can perform on a Cart\nexport interface ICart {\n    createCart: () =\u003e Cart;\n    addProductToCart: (cart: Cart, product: Product) =\u003e Cart;\n    removeProductFromCart: (cart: Cart, product: Product) =\u003e Cart;\n}\n\nexport type Cart = {\n    id: string;\n    products: Product[];\n};\n\n```\n\nNow we define a functionality that allow us to add and remove a \"Product\", keeping in mind the requirements and business rules.\n\nDepending on the design pattern we use this implementation might be slightly different. For this case we use an easy option, a service module that handle the data.\n\n```ts\n// src/domain/services/Cart.service.ts\n\nimport { Cart, ICart } from '../models/Cart';\nimport { Product } from '../models/Product';\n\nconst createCart = (): Cart =\u003e {\n    return { id: Date.now().toString(), products: [] };\n};\n\nconst hasProduct = (cart: Cart, product: Product): boolean =\u003e {\n    return !!cart.products.find(item =\u003e item.id === product.id);\n};\n\nconst isCartFull = (cart: Cart): boolean =\u003e {\n    return cart.products.length \u003e= 5;\n};\n\nconst isCartLimitPriceExceeded = (cart: Cart, product: Product, limit: number): boolean =\u003e {\n    let totalPriceCart = 0;\n    cart.products.forEach(item =\u003e {\n        totalPriceCart += item.price;\n    });\n    totalPriceCart += product.price;\n\n    return totalPriceCart \u003e limit;\n};\n\nconst addProductToCart = (cart: Cart, product: Product): Cart =\u003e {\n    if (!hasProduct(cart, product) \u0026\u0026 !isCartFull(cart) \u0026\u0026 !isCartLimitPriceExceeded(cart, product, 100))\n        cart.products = [...cart.products, product];\n    return { ...cart };\n};\n\nconst removeProductFromCart = (cart: Cart, product: Product): Cart =\u003e {\n    const productsWithRemovedItem: Product[] = [];\n    cart.products.forEach(item =\u003e {\n        if (item.id !== product.id) productsWithRemovedItem.push(item);\n    });\n    cart.products = [...productsWithRemovedItem];\n    return { ...cart };\n};\n\n// This service must implement the operations defined for the Cart interface\nexport const cartService: ICart = {\n    createCart,\n    addProductToCart,\n    removeProductFromCart\n};\n\n```\n\n\u003ca name=\"examData\"\u003e\u003c/a\u003e\n### Data access :newspaper:\n\nAlso, we need to get a list of products. In most cases this data is obtained from http services, but we could also use graphql or any other library. Moreover, within http we could use fetch, axios, xhr...\n\nIn any case, this is part of the infrastructure layer, and this object will be consumed by a repository entity.\n\nFirst, we define the structure of data returned by API. This kind of data is called \"Data Transfer Object (DTO)\":\n\n```ts\n// src/infrastructure/http/dto/ProductDTO.ts\n\nexport interface ProductDTO {\n    id: string;\n    title: string;\n    description: string;\n    price: number;\n}\n\n```\n\nFurthermore, we have declared what methods we need implement for http. So, later we will be able to use our favourite client (fetch, axios...) implementing this interface:\n\n```ts\n// src/domain/repositories/Http.ts\n\nexport interface Http {\n    get: \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e Promise\u003cT | any\u003e;\n    post: \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e Promise\u003cT | any\u003e;\n    put: \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e Promise\u003cT | any\u003e;\n    delete: \u003cT\u003e(path: string, params?: any, config?: any) =\u003e Promise\u003cT | any\u003e;\n}\n\n```\n\nOur client, which will be an instance that implements the `Http` interface, will be injected as a dependency to our repository. Thus, at any time, we will be able to change our client instantly. This technique is called dependency injection, and although it is not very common in javascript, it is very powerful and is there to be used. Typescript makes it very easy for us to do it.\n\nFor this example we have created two clients (wrappers), one using axios, and the other will return a mock data.\n\n#### Client for axios\n```ts\n// src/infrastructure/instances/httpAxios.ts\n\nimport axios from 'axios';\nimport { Http } from '../../domain/repositories/Http';\n\nconst headers = {\n    'Content-Type': 'application/json'\n};\n\nexport const httpAxios: Http = {\n    get: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {\n        const response = await axios.get(path, { ...config, params: params, headers });\n        return response.data as T;\n    },\n    post: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {\n        const response = await axios.post(path, { ...params }, { ...config, headers });\n        return response.data as T;\n    },\n    put: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {\n        const response = await axios.put(path, { ...params }, { ...config, headers });\n        return response.data as T;\n    },\n    delete: async \u003cT\u003e(path: string, params?: any, config?: any) =\u003e {\n        const response = await axios.delete(path, { ...config, params: params, headers });\n        return response.data as T;\n    }\n};\n```\n\n#### Client for data fake\n```ts\n// src/infrastructure/instances/httpAxios.ts\n\nimport { Http } from '../../domain/repositories/Http';\nimport { productListMock } from '../../mocks/products';\n\nexport const httpFake: Http = {\n    get: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {\n        const response = await productListMock;\n        return response;\n    },\n    post: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {\n        const response = await productListMock;\n        return response;\n    },\n    put: async \u003cT\u003e(path: string, params?: Record\u003cstring, any\u003e, config?: any) =\u003e {},\n    delete: async \u003cT\u003e(path: string, params?: any, config?: any) =\u003e {}\n};\n```\n\nIn this way, when you want to change the client and use, for example, fetch instead of axios, you can create a new http wrapper that implement the interface for http, but using fetch library. Easy!\n\nTo finalize this part, we need a last thing. We have to create a repository for products inside infrastructure. This repository handle the request and, the transformation of response data to our domain model.\n\n```ts\n// src/infrastructure/repositories/productRepository.ts\n\nimport { Product } from '../../domain/models/Product';\nimport { ProductRepository } from '../../domain/repositories/ProductRepository';\nimport { Http } from '../../domain/repositories/Http';\nimport { ProductDTO } from '../../infrastructure/http/dto/ProductDTO';\n\nexport const productRepository = (client: Http): ProductRepository =\u003e ({\n    getProducts: async () =\u003e {\n        const products = await client.get\u003cProductDTO\u003e('');\n        return products.map((productDto): Product =\u003e ({ id: productDto.id, title: productDto.title, price: productDto.price }));\n    },\n\n    getProductsById: async id =\u003e {\n        const products = await client.get\u003cProductDTO\u003e('', { id });\n        return products.map((productDto): Product =\u003e ({ id: productDto.id, title: productDto.title, price: productDto.price }));\n    }\n});\n```\n\nAs you can see, `productRepository` is a function that receive a client as parameter (just here is the dependency injection).\n\nFor convenience, we have created a fake repository that implement the interface `ProductRepository`.\n\n**Remember that production code mustn't contain any reference to fake or mock data. We are using it for making this project functional. For this reason, in production you must forget the `instances` folder.**\n\n\n\u003ca name=\"examViews\"\u003e\u003c/a\u003e\n### Views :iphone: :computer:\n\nThe view and layer to access to data are in infrastructure. However, they mustn't communicate directly. We are going to create a new service to consume our repository, so this data will available for the rest of our application.\n\n```ts\n// src/domain/services/ProductService.ts\n\nimport { ProductRepository } from '../repositories/ProductRepository';\n\n// Here we can change the repository by one that implement the IProductRepository interface\n//const repository: IProductRepository = productRepository;\n\nexport const productService = (repository: ProductRepository): ProductRepository =\u003e ({\n    getProducts: () =\u003e {\n        return repository.getProducts();\n    },\n    getProductsById: id =\u003e {\n        return repository.getProductsById(id);\n    }\n});\n```\n\nAs with our `Http` client, we are using dependecy injection in our service, which receive a repository as parameter. In this way, we will be able to change the repository at any time. (Maybe in the future, we have to obtain the products from a local database instead of a rest service). \n\n---\n\nWell, now we have defined how to obtain the data, and the functionality we need to add/remove elements in our cart.\n\nRegardless of whether you use react or vue, note that the code wrote so far is common to both apps, so the call to our methods from our component will be the same.\n\nInside views folder we have created as many projects as we have needed. In our case we have react, vue and react native.\n\n\nThe first thing we are going to do know is define the initial state and, the functions to handle the cart state.\n\n#### React\n```tsx\n// src/infrastructure/views/react-ui/src/App.tsx\n\nimport React, { useState } from 'react';\nimport { ProductList } from './views/ProductList';\nimport { Cart } from '@domain/models/Cart';\nimport { Product } from '@domain/models/Product';\nimport { cartService } from '@domain/services/CartService';\n\nconst App = () =\u003e {\n    const [cart, setCart] = useState\u003cCart\u003e(cartService.createCart());\n\n    const handleAddToCart = (product: Product) =\u003e {\n        setCart(cartService.addProductToCart(cart, product));\n    };\n\n    const handleRemoveToCart = (product: Product) =\u003e {\n        setCart(cartService.removeProductFromCart(cart, product));\n    };\n\n    const renderCartProducts = (): JSX.Element[] =\u003e {\n        const cartProducts: JSX.Element[] = [];\n        let totalCart = 0;\n\n        cart.products.forEach(product =\u003e {\n            totalCart += product.price;\n            cartProducts.push(\n                \u003cdiv key={product.id}\u003e\n                    \u003clabel\u003e{product.title} \u003c/label\u003e\n                    \u003cspan\u003e({product.price} €) \u003c/span\u003e\n                    \u003cbutton onClick={() =\u003e handleRemoveToCart(product)}\u003eremove\u003c/button\u003e\n                    \u003cbr /\u003e\n                \u003c/div\u003e\n            );\n        });\n\n        cartProducts.push(\n            \u003cdiv key={'total'}\u003e\n                \u003cbr /\u003e\n                \u003clabel\u003e\n                    \u003cb\u003eTotal:\u003c/b\u003e\n                \u003c/label\u003e\n                \u003cspan\u003e{totalCart} €\u003c/span\u003e\n                \u003cbr /\u003e\n            \u003c/div\u003e\n        );\n        return cartProducts;\n    };\n\n    return (\n        \u003cdiv\u003e\n            \u003ch1\u003eShopping cart\u003c/h1\u003e\n            \u003ch2\u003eProducts in the cart\u003c/h2\u003e\n            {renderCartProducts()}\n            \u003cProductList onSelectProduct={handleAddToCart} /\u003e\n        \u003c/div\u003e\n    );\n};\n\nexport default App;\n```\n\n#### Vue\n```vue\n\u003c!--\n// src/infrastructure/views/vue-ui/src/App.vue\n--\u003e\n\u003ctemplate\u003e\n    \u003cdiv id=\"app\"\u003e\n        \u003ch1\u003eShopping cart\u003c/h1\u003e\n        \u003ch2\u003eProducts in the cart\u003c/h2\u003e\n        \u003cdiv v-for=\"product in cart.products\" :key=\"product.id\"\u003e\n            \u003clabel\u003e{{ product.title }} \u003c/label\u003e\n            \u003cspan\u003e({{ product.price }} €) \u003c/span\u003e\n            \u003cbutton @click=\"handleRemoveProductFromCart(product)\"\u003eremove\u003c/button\u003e\n            \u003cbr /\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n            \u003cbr /\u003e\n            \u003clabel\u003e\n            \u003cb\u003eTotal:\u003c/b\u003e\n            \u003c/label\u003e\n            \u003cspan\u003e{{ getTotalCart() }} €\u003c/span\u003e\n            \u003cbr /\u003e\n        \u003c/div\u003e\n        \u003cProductList @onSelectProduct=\"handleAddProductToCart\" /\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript lang=\"ts\"\u003e\nimport { Product } from '@/domain/models/Product';\nimport ProductList from '@/infrastructure/views/ProductList.vue';\nimport { cartService } from '@/domain/services/Cart.service';\nimport { Cart } from '@/domain/models/Cart';\n\ntype DataProps = {\n    cart: Cart;\n};\n\nexport default {\n    components: {\n        ProductList\n    },\n    data(): DataProps {\n        return {\n            cart: cartService.createCart()\n        };\n    },\n    mounted() {\n        this.cart = cartService.createCart();\n    },\n    methods: {\n        handleAddProductToCart(product: Product) {\n            this.cart = cartService.addProductToCart(this.cart, product);\n        },\n        handleRemoveProductFromCart(product: Product) {\n            this.cart = cartService.removeProductFromCart(this.cart, product);\n        },\n        getTotalCart() {\n            let totalCart = 0;\n            this.cart.products.forEach(product =\u003e {\n                totalCart += product.price;\n            });\n            return totalCart;\n        }\n    }\n};\n\u003c/script\u003e\n```\n\n#### React Native\n```tsx\n// src/infrastructure/views/reactnative-ui/App.tsx\n\nimport React, { useState } from 'react';\nimport { SafeAreaView, StyleSheet, ScrollView, View, Text, StatusBar, Button } from 'react-native';\n\nimport { Colors } from 'react-native/Libraries/NewAppScreen';\nimport { Cart } from '@domain/models/Cart';\nimport { cartService } from '@domain/services/CartService';\nimport { Product } from '@domain/models/Product';\nimport { ProductList } from '@/components/ProductList';\n\nconst App = () =\u003e {\n    const [cart, setCart] = useState\u003cCart\u003e(cartService.createCart());\n\n    const handleAddToCart = (product: Product) =\u003e {\n        setCart(cartService.addProductToCart(cart, product));\n    };\n\n    const handleRemoveToCart = (product: Product) =\u003e {\n        setCart(cartService.removeProductFromCart(cart, product));\n    };\n\n    const renderCartProducts = (): JSX.Element[] =\u003e {\n        const cartProducts: JSX.Element[] = [];\n        let totalCart = 0;\n\n        cart.products.forEach(product =\u003e {\n            totalCart += product.price;\n            cartProducts.push(\n                \u003cView style={styles.productInCart} key={product.id}\u003e\n                    \u003cText\u003e{product.title} \u003c/Text\u003e\n                    \u003cText\u003e({product.price} €) \u003c/Text\u003e\n                    \u003cButton color={'red'} onPress={() =\u003e handleRemoveToCart(product)} title={'remove'} /\u003e\n                \u003c/View\u003e\n            );\n        });\n\n        cartProducts.push(\n            \u003cView style={styles.productInCart} key={'total'}\u003e\n                \u003cText\u003eTotal:\u003c/Text\u003e\n                \u003cText\u003e {totalCart} €\u003c/Text\u003e\n            \u003c/View\u003e\n        );\n        return cartProducts;\n    };\n\n    return (\n        \u003c\u003e\n            \u003cStatusBar barStyle='default' /\u003e\n            \u003cSafeAreaView\u003e\n                \u003cScrollView contentInsetAdjustmentBehavior='automatic' style={styles.scrollView}\u003e\n                    \u003cText style={styles.titlePage}\u003eShopping cart\u003c/Text\u003e\n                    \u003cText style={styles.title}\u003eProducts in the car\u003c/Text\u003e\n                    {renderCartProducts()}\n                    \u003cProductList onSelectProduct={handleAddToCart} /\u003e\n                \u003c/ScrollView\u003e\n            \u003c/SafeAreaView\u003e\n        \u003c/\u003e\n    );\n};\n\nconst styles = StyleSheet.create({\n    scrollView: {\n        backgroundColor: Colors.lighter\n    },\n    titlePage: {\n        fontWeight: 'bold',\n        margin: 5,\n        fontSize: 20\n    },\n    title: {\n        fontWeight: 'bold',\n        margin: 5\n    },\n    productInCart: {\n        flexDirection: 'row',\n        flex: 1,\n        alignContent: 'center',\n        alignItems: 'center',\n        margin: 10\n    }\n});\n\nexport default App;\n\n```\n\nWe are going to show the list of products.\n\n#### React\n\n```tsx\n// src/infrastructure/views/react-ui/src/views/ProductList.tsx\n\nimport React, { useCallback } from 'react';\nimport { Product } from '@domain/models/Product';\nimport { productService } from '@domain/services/ProductService';\nimport { productRepositoryFake } from '@infrastructure/instances/productRepositoryFake';\n\ninterface ProductListProps {\n    onSelectProduct: (product: Product) =\u003e void;\n}\n\nexport const ProductList: React.FC\u003cProductListProps\u003e = ({ onSelectProduct }) =\u003e {\n    const [products, setProducts] = React.useState\u003cProduct[]\u003e([]);\n\n    const getProducts = useCallback(async () =\u003e {\n        try {\n            const responseProducts = await productService(productRepositoryFake).getProducts();\n            setProducts(responseProducts);\n        } catch (exception) {\n            console.error(exception);\n        }\n    }, []);\n\n    React.useEffect(() =\u003e {\n        getProducts();\n    }, []);\n\n    const handleSelectProduct = (product: Product) =\u003e {\n        onSelectProduct(product);\n    };\n\n    return (\n        \u003cdiv\u003e\n            \u003ch2\u003eList of products\u003c/h2\u003e\n            \u003cul\u003e\n                {products.map(product =\u003e (\n                    \u003cli key={product.id}\u003e\n                        \u003cbutton\n                            onClick={() =\u003e {\n                                handleSelectProduct(product);\n                            }}\n                        \u003e\n                            {product.title}\n                        \u003c/button\u003e\n                    \u003c/li\u003e\n                ))}\n            \u003c/ul\u003e\n        \u003c/div\u003e\n    );\n};\n```\n\n#### Vue\n```vue\n\u003c!--\n// src/infrastructure/views/vue-ui/src/views/ProductList.vue\n--\u003e\n\u003ctemplate\u003e\n    \u003cdiv\u003e\n        \u003ch2\u003eList of products\u003c/h2\u003e\n        \u003cul\u003e\n            \u003cli v-for=\"product in products\" :key=\"product.id\"\u003e\n                \u003cbutton @click=\"handleSelectProduct(product)\"\u003e{{ product.title }}\u003c/button\u003e\n            \u003c/li\u003e\n        \u003c/ul\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript lang=\"ts\"\u003e\nimport { productService } from '@domain/services/ProductService';\nimport { Product } from '@domain/models/Product';\nimport { productRepositoryFake } from '@infrastructure/instances/productRepositoryFake';\n\ntype DataProps = {\n    products: Product[];\n};\n\nexport default {\n    name: 'ProductList',\n    data(): DataProps {\n        return {\n            products: []\n        };\n    },\n    mounted() {\n        productService(productRepositoryFake)\n            .getProducts()\n            .then(response =\u003e (this.products = response));\n    },\n    methods: {\n        handleSelectProduct(product: Product) {\n          this.$emit('onSelectProduct', product);\n        }\n    }\n};\n\u003c/script\u003e\n```\n\n#### React Native\n```tsx\n// src/infrastructure/views/reactnative-ui/src/components/ProductList.tsx\n\nimport React, { useCallback, useState } from 'react';\nimport { StyleSheet, View, Text, Button } from 'react-native';\n\nimport { Product } from '@domain/models/Product';\nimport { productService } from '@domain/services/ProductService';\nimport { productRepositoryFake } from '@infrastructure/instances/productRepositoryFake';\n\ninterface ProductListProps {\n    onSelectProduct: (product: Product) =\u003e void;\n}\n\nexport const ProductList: React.FC\u003cProductListProps\u003e = ({ onSelectProduct }) =\u003e {\n    const [products, setProducts] = useState\u003cProduct[]\u003e([]);\n\n    const getProducts = useCallback(async () =\u003e {\n        try {\n            const responseProducts = await productService(productRepositoryFake).getProducts();\n            setProducts(responseProducts);\n        } catch (exception) {\n            console.error(exception);\n        }\n    }, []);\n\n    React.useEffect(() =\u003e {\n        getProducts();\n    }, []);\n\n    const handleSelectProduct = (product: Product) =\u003e {\n        onSelectProduct(product);\n    };\n\n    return (\n        \u003cView\u003e\n            \u003cText style={styles.title}\u003eList of products\u003c/Text\u003e\n            \u003cView\u003e\n                {products.map(product =\u003e (\n                    \u003cView style={styles.buttonProduct} key={product.id}\u003e\n                        \u003cButton\n                            onPress={() =\u003e {\n                                handleSelectProduct(product);\n                            }}\n                            title={product.title}\n                        \u003e\n                            \u003cText\u003e{product.title}\u003c/Text\u003e\n                        \u003c/Button\u003e\n                    \u003c/View\u003e\n                ))}\n            \u003c/View\u003e\n        \u003c/View\u003e\n    );\n};\n\nconst styles = StyleSheet.create({\n    title: {\n        fontWeight: 'bold',\n        margin: 5\n    },\n    buttonProduct: {\n        margin: 5\n    }\n});\n```\n\nThe state's management of cart's elements is interesting, but it is something related with the data visualization and this management belongs to the technology we are using (React or Vue).\n\nFurthermore, if we pay attention to above code, we can see that all our code is decoupled. Our domain layer can be used by react, vue and react native.\n\nAlso, here we can see the dependency injection in action.\nWe use the product service, which receives a specific `ProductRepository` repository that, in turn, receives a specific `Http` client.\n\n\u003ca name=\"examThird\"\u003e\u003c/a\u003e\n### Third party libraries :hammer_and_wrench:\n\nLet's go to do a quick review about one library we have used, in this case axios. Our **domain** mustn't know anything about the existence of this library, I mean, we should be able to switch another library without affecting our **domain**.\nI'm going to explain step by step:\n\n- We have defined an interface `ProductRepository` that declare what methods have to be implemented by our repository.\n\n\n- We have defined an interface `Http` that declare what methods have to be implemented by our client (a web service in this case).\n\n\n- We have instantiated a `productRepository` that implement the interface `ProductRepository`.\n\n\n- We have instantiated a `httpAxios` that implement the interface `Http`.\n\n\n- Our `productRepository` instance uses an instance of `httpAxios`. If at any time you want to change the http library, for example, to fetch, you only have to write another client that implement the interface `Http` and pass it to the repository.\n\n\n- Even if at any point you want to change the repository, for example to a local database, you could write another repository that implement the interface `ProductRepository`.\n\n\nDoing that, you have applied the dependency inversion principle, so now, you have a code low coupled, and with high cohesion.\n\n\u003ca name=\"examTest\"\u003e\u003c/a\u003e\n### Tests :heavy_check_mark:\n\nOf course, one thing you can't miss are the tests. Tests are important, even crucial. Yes I know, maybe you are thinking...Why would I want to write test if I can try the system manually?.\nWell, it's very common that your application grow over time, so sure you will have to change some parts of your app. Then it becomes very tedious come back to test everything manually. Writing tests is the best way to be sure that our system works, and also, if some future changes have affected or broken our application.\n\nKeep that in mind, many people have a percentage of test coverage above which they consider it sufficient. What do you think? Perhaps an 80% is right? Well, then you have a 20% of your system that can fail. There are no excuse to do things well, the only percentage of test coverage which ensures that our application works as you want is 100%.\n\nEven if you have a 100% of code coverage, it doesn't assure you that you are covering all the cases.\nHere a little example:\n\n`if (a \u003e 1) ? 'Hi' : 'Bye'`\n\nIn this case you can make a test that check it return `Hi`, but if you don't test the return of `Bye` you have a 100% of coverage, but your code is not completely tested.\n**So, please, pay attention to all cases and try to do smart tests.**\n\nYes, I was one of them. I have written code without tests, but not because I didn't want to do tests, the problem was that having all the code scattered and mixed with the views, it was difficult if not impossible to write tests.\n\nSo, let's go write some test. You will see how easy is writing test after having implemented an appropriate architecture...having **domain** separated from **infrastructure**\n\nTo do test we use `jest`. From my point of view is the ultimate library to do testing in javascript.\n\nFirst we are going to take the business rules (described in the introduction of this example, see above), and we are going to test each of them.\n\n```ts\n// src/tests/cart.test.ts\n\nimport { cartService } from '../domain/services/CartService';\nimport { Product } from '../domain/models/Product';\n\nconst anyProduct = (id: string, price: number): Product =\u003e ({\n    id,\n    title: 'Any title',\n    price\n});\n\ntest('A car can not contain more than 5 products', async () =\u003e {\n    const cart = cartService.createCart();\n\n    cartService.addProductToCart(cart, anyProduct('1', 0));\n    cartService.addProductToCart(cart, anyProduct('2', 0));\n    cartService.addProductToCart(cart, anyProduct('3', 0));\n    cartService.addProductToCart(cart, anyProduct('4', 0));\n    cartService.addProductToCart(cart, anyProduct('5', 0));\n    cartService.addProductToCart(cart, anyProduct('6', 0));\n    expect(cart.products.length).toEqual(5);\n});\n\ntest('If I add a product and it already exist in the cart, the product will not be added', async () =\u003e {\n    const cart = cartService.createCart();\n\n    cartService.addProductToCart(cart, anyProduct('1', 0));\n    cartService.addProductToCart(cart, anyProduct('1', 0));\n    expect(cart.products.length).toEqual(1);\n});\n\ntest('If I add a product and it will exceed 100€, the product will not be added', async () =\u003e {\n    const cart = cartService.createCart();\n\n    cartService.addProductToCart(cart, anyProduct('1', 50));\n    cartService.addProductToCart(cart, anyProduct('2', 60));\n    expect(cart.products.length).toEqual(1);\n});\n\n```\n\nWe could do more tests, but that is not the main purpose of this tutorial.\n\n\u003ca name=\"finalWords\"\u003e\u003c/a\u003e\n## Final words :hugs:\n\nAs we have seen, implementing a good architecture will allow us to improve the code maintenance. Also, we will be decoupled from the framework/library we are using, adding more value to the **domain**.\n\nDon't forget that this pattern can be combined with other concepts, like DDD, Functional Programing...\nNotwithstanding, the purpose of this example is  to give a basic idea of how we could implement hexagonal architecture in a frontend project.\n\nI hope all this will help you in your day-to-day work and that, applying this or other patterns, you can improve the quality of your work.\n\nIf you have any doubt you can contact me through my email or leaving a comment here (for example in the [discussions](https://github.com/juanm4/hexagonal-architecture-frontend/discussions) section).\n\nIf you liked it share it...and remember:\n\nWE RULE THE WORLD!\n\n\u003ca name=\"bonus\"\u003e\u003c/a\u003e\n## What does include this project? (Extra bonus) :heart:\n\n- This project is ready to use, you can download and execute it.\n\n\n- I hate relatives routes, they make code hard to follow. So this project prepared to use relative paths using `@alias` in `tsconfig.json` and `babel-plugin-module-resolver`.\n\n\n- Code formatted with `eslint` and `prettier`.\n  \n\n- To execute react project go to `src/infrastructure/views/react-ui/` and execute `npm install \u0026\u0026 npm run start`.\n\n\n- To execute vue project go to `src/infrastructure/views/vue-ui/` and execute `npm install \u0026\u0026 npm run serve`.\n\n\n- To execute react native project go to `src/infrastructure/views/reactnative-ui/` and execute `npm install \u0026\u0026 npm run start`. Then execute `npm run android`.\n\n\n- All configuration files are included. Take a look at them.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanm4%2Fhexagonal-architecture-frontend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuanm4%2Fhexagonal-architecture-frontend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanm4%2Fhexagonal-architecture-frontend/lists"}