Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 13, 2022 10:57 pm GMT

Vue.js, Clean Architecture e Package by feature Pattern

L vamos ns de novo falar de clean architecture... Mas agora no vamos mais falar de golang, e sim de Vue.js. Vamos implementar o fronted da nossa api da srie de Clean Architecture com Golang em Vue.js.

Vamos l! Nossa implementao do frontend deve ter os mesmos requisitos da nossa api:

  • Uma listagem de produtos
  • Um formulrio para adicionar produtos na lista

Package by feature Pattern

Nesta estrutura de projeto, os pacotes contm todas as classes necessrias para um recurso. A independncia do pacote assegurada colocando classes intimamente relacionadas no mesmo pacote. Aqui um post com um timo exemplo de como funciona.

Implementao

Primeira coisa que precisamos fazer criar nosso projeto vue, ainda com o Vue 2 com typescript.

vue create clean-vuecd clean-vuevue add vuetifynpm i axiosnpm i @types/axios --save-devnpm run serve

Projetinho rodando liso, bora codar!

Vamos apagar a pasta src/components e estruturar o projeto da seguinte forma:

  • src
    • di
    • module
      • pagination
        • domain
          • model
      • product
        • const
        • components
        • repository
        • domain
          • model
          • usecase
        • controller
        • view

Agora tudo est dando erro, nada mais funciona! Calma l, vamos estruturar nosso cdigo que tudo se resolve. :D

Model

Primeira coisa ns definirmos o model com o que volta da API no arquivo src/module/product/domain/model/product.ts.

import { AxiosResponse } from "axios"interface ProductI {    id?: number    name?: string    description?: string    price?: number}class Product {    id: number    name: string    description: string    price: number    constructor({ id = 0, name = "", description = "", price = 0.00 }: ProductI) {        this.id = id        this.name = name        this.description = description        this.price = price    }}class ProductPagination {    items: ProductI[]    total: number    constructor(response?: AxiosResponse) {        this.items = response?.data?.items?.map((product: any) => new Product(product)) ?? []        this.total = response?.data?.total ?? 0    }}export { Product, ProductPagination }

E tambm o model de paginao default de toda a aplicao no arquivo src/module/pagination/domain/model/pagination.ts.

interface PaginationI {    page: number    itemsPerPage: number    sort: string    descending: string    search: string}class Pagination {    page: number    itemsPerPage: number    sort: string    descending: string    search: string    constructor({ page, itemsPerPage, sort, descending, search }: PaginationI) {        this.page = page        this.itemsPerPage = itemsPerPage        this.descending = descending        this.search = search        this.sort = sort    }}export { Pagination } 

Repository

Com nossos models prontos, podemos j preparar nosso repository para manipular os endpoint dos nossos produtos.
Criaremos o arquivo src/module/product/repository/fetchProductsRepository.ts.

import { Pagination } from '@/module/pagination/domain/model/pagination'import { ProductPagination } from '../domain/model/product'import { AxiosInstance } from 'axios'interface FetchProductsRepository {    (pagination: Pagination): Promise<ProductPagination>}const fetchProductsRepository = (axios: AxiosInstance): FetchProductsRepository => async (pagination: Pagination) => {    const response = await axios.get("/product", {        params: pagination    })    const productPagination = new ProductPagination(response)    return productPagination}export { fetchProductsRepository, FetchProductsRepository } 

E tambm criaremos o arquivo src/module/product/repository/createProductRepository.ts.

import { Product } from '../domain/model/product'import { AxiosInstance } from 'axios'interface CreateProductRepository {    (product: Product): Promise<Product>}const createProductRepository = (axios: AxiosInstance): CreateProductRepository => async (product: Product) => {    const response = await axios.post("/product", product)    return new Product(response?.data)}export { createProductRepository, CreateProductRepository } 

Usecase

Com nossos repositories criados, podemos implementar nosso usecase de produtos.
Criaremos o arquivo src/module/product/domain/usecase/fetchProductsUseCase.ts.

import { FetchProductsRepository } from "../../repository/fetchProductsRepository"import { Pagination } from "@/module/pagination/domain/model/pagination"import { ProductPagination } from "../model/product"import { DataOptions } from "vuetify"interface FetchProductsUseCase {    (options: DataOptions, search: string): Promise<ProductPagination>}const fetchProductsUseCase = (repository: FetchProductsRepository): FetchProductsUseCase => async (options: DataOptions, search: string) => {    const pagination = new Pagination({        descending: options.sortDesc.join(","),        sort: options.sortBy.join(","),        page: options.page,        itemsPerPage: options.itemsPerPage,        search: search,    })    const productPagination = await repository(pagination)    return productPagination}export { fetchProductsUseCase, FetchProductsUseCase } 

E tambm criaremos o arquivo src/module/product/domain/usecase/createProductUseCase.ts.

import { CreateProductRepository } from "../../repository/createProductRepository"import { Product } from "../model/product"interface CreateProductsUseCase {    (product: Product): Promise<Product>}const createProductUseCase = (repository: CreateProductRepository): CreateProductsUseCase => async (product: Product) => {    const productCreated = await repository(product)    return productCreated}export { createProductUseCase, CreateProductsUseCase } 

Controller

Com nossos usecases criados, podemos implementar nosso Controller no arquivo module/product/controller/productController.ts.

import { CreateProductsUseCase } from "../domain/usecase/createProductUseCase";import { FetchProductsUseCase } from "../domain/usecase/fetchProductUseCase";import { Product, ProductPagination } from "../domain/model/product";import { headers } from "../const/header";class ProductController {    options: any    public product = new Product({})    public productPagination = new ProductPagination()    public headers = headers    public formDialog = false    constructor(        private context: any,        private fetchProductsUseCase: FetchProductsUseCase,        private createProductUseCase: CreateProductsUseCase    ) { }    async paginate() {        this.productPagination = await this.fetchProductsUseCase(this.options, "")    }    async save() {        if (this.context.$refs.productForm.$refs.form.validate()) {            await this.createProductUseCase(this.product)            this.cancel()            this.paginate()        }    }    cancel() {        this.product = new Product({})        this.context.$refs.productForm.$refs.form.resetValidation()        this.formDialog = false    }}export { ProductController }

Tudo pronto! Brincadeira... Estamos quase l, vamos configurar nossa injeo de dependncias. Para configurar a injeo de dependncia do nosso product vamos criar um arquivo em module/di/di.ts.

import { fetchProductsRepository } from "../product/repository/fetchProductsRepository";import { createProductRepository } from "../product/repository/createProductRepository";import { createProductUseCase } from "../product/domain/usecase/createProductUseCase";import { fetchProductsUseCase } from "../product/domain/usecase/fetchProductUseCase";import { ProductController } from "../product/controller/productController";import axios from "axios";const axiosInstance = axios.create({    baseURL: "https://clean-go.herokuapp.com",    headers: {        "Content-Type": "application/json"    }})axiosInstance.interceptors.response.use((response) => response, async (err) => {    const status = err.response ? err.response.status : null    if (status === 500) {        // Do something here or on any status code return    }    return Promise.reject(err);});// Implementation methods from products featureconst fetchProductsRepositoryImpl = fetchProductsRepository(axiosInstance)const fetchProductsUseCaseImpl = fetchProductsUseCase(fetchProductsRepositoryImpl)const createProductRepositoryImpl = createProductRepository(axiosInstance)const createProductUseCaseImpl = createProductUseCase(createProductRepositoryImpl)const productController = (context: any) => new ProductController(    context,    fetchProductsUseCaseImpl,    createProductUseCaseImpl)export { productController }

Agora sim, bora para nossa tela! Fique a vontade para montar ela do jeito que quiser!

Criaremos o arquivo module/product/components/productTable.vue

<template>  <v-card>    <v-card-title>      Products      <v-spacer></v-spacer>      <v-btn        color="primary"        @click="controller.formDialog = true"      >        <v-icon left>mdi-plus</v-icon>new      </v-btn>    </v-card-title>    <v-card-text class="pa-0">      <v-data-table        dense        :items="controller.productPagination.items"        :headers="controller.headers"        :options.sync="controller.options"        @pagination="controller.paginate()"        :server-items-length="controller.productPagination.total"      ></v-data-table>    </v-card-text>  </v-card></template><script>export default {  props: {    controller: {      require: true,    },  },};</script>

E o arquivo module/product/components/productForm.vue

<template>  <v-dialog    persistent    width="400"    v-model="controller.formDialog"  >    <v-card>      <v-card-title class="pa-0 pb-4">        <v-toolbar          flat          dense          color="primary"          class="white--text"        >          New product        </v-toolbar>      </v-card-title>      <v-card-text>        <v-form ref="form">          <v-text-field            label="Name"            dense            filled            v-model="controller.product.name"            :rules="[(v) => !!v || 'Required']"          ></v-text-field>          <v-text-field            label="Price"            dense            filled            v-model.number="controller.product.price"            :rules="[(v) => !!v || 'Required']"          ></v-text-field>          <v-textarea            label="Description"            dense            filled            v-model="controller.product.description"            :rules="[(v) => !!v || 'Required']"          ></v-textarea>        </v-form>      </v-card-text>      <v-card-actions>        <v-btn          @click="controller.cancel()"          color="red"          text        >cancel</v-btn>        <v-spacer></v-spacer>        <v-btn          color="primary"          @click="controller.save()"        >          <v-icon left>mdi-content-save</v-icon>save        </v-btn>      </v-card-actions>    </v-card>  </v-dialog></template><script>export default {  props: {    controller: {      require: true,    },  },};</script>

E por fim criaremos o arquivo module/product/view/product.vue

<template>  <v-app>    <v-main>      <v-row        class="fill-height"        justify="center"        align="center"      >        <v-col          cols="12"          lg="6"        >          <product-table            ref="productTable"            :controller="controller"          />          <product-form            ref="productForm"            :controller="controller"          />        </v-col>      </v-row>    </v-main>  </v-app></template><script>import { productController } from "../../di/di";import ProductTable from "../components/productTable";import ProductForm from "../components/productForm";export default {  components: {    ProductTable,    ProductForm,  },  data: (context) => ({    controller: productController(context),  }),};</script>

E a estrutura final ficou:

Testando, 1..2..3.. Teste som!


Original Link: https://dev.to/booscaaa/vuejs-clean-architecture-e-package-by-feature-pattern-56lb

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To