An Interest In:
Web News this Week
- April 25, 2024
- April 24, 2024
- April 23, 2024
- April 22, 2024
- April 21, 2024
- April 20, 2024
- April 19, 2024
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
- domain
- product
- const
- components
- repository
- domain
- model
- usecase
- controller
- view
- pagination
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
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To