DEV Community

Cover image for Facade Pattern no Angular
Mateus Oliveira
Mateus Oliveira

Posted on

2

Facade Pattern no Angular

Você alguma vez já ouviu falar sobre o Padrão Facade no Angular ?
Recentemente me peguei em um desafio interessante sobre este tema, precisei mergulhar neste universo. Quero compartilhar com voces um poquinho sobre esse padrão. Bora lá ?

Os exemplos contidos neste artigo, são baseados em cenários reais👻

O que é Facade Pattern?

O Facade Pattern é um padrão de design estrutural, com o objetivo de simplificar a interação com o sistema. Em vez de expor a complexidade de diferentes componentes ou serviços, a Facade propõe uma interface simplificada, fazendo a ponte entre o cliente (eu, você desenvolvedor) e a aplicação.

Em outras palavras, a Facade funciona como uma "fachada" ou "porta de entrada" (daí o nome! rs) para um conjunto de funcionalidades complexas, a famosa regra de negocio da nossa aplicação. Ela esconde todos os detalhes de implementação e expõe apenas o necessário para que nós possamos usar, sem se preocupar com o que tá rolando por trás da cena.

Quando usar esse Padrão?

O Padrão Facade é particularmente útil varios cenários, onde gerenciar a complexidade e promover uma arquitetura limpa são preocupações-chave. Abaixo estão algumas situações onde esse padrão pode ser altamente benéfico:

  • Sistemas complexos : Use quando seu aplicativo tiver vários subsistemas complexos. A Facade esconde essas complexidades fornecendo uma interface simples, tornando o código do cliente mais fácil de usar e entender.
  • Dependências acopladas: se os componentes estiverem fortemente acoplados a vários serviços, uma Facade pode desvinculá-los, tornando a base de código mais flexível e fácil de manter.
  • Organização do código : em grandes projetos, a Facade centraliza as interações com subsistemas em uma interface coesa, melhorando a legibilidade e a manutenção.
  • Testabilidade: as Facades simplificam os testes permitindo que você simule uma única Facade em vez de vários serviços, tornando os testes mais confiáveis ​​e menos complexos.

Resumindo galera, use esse padrão para simplificar interações complexas, reduzir o acoplamento e melhorar a capacidade de manutenção e testabilidade do seu código.

Por que usar o Facade Pattern no Angular?

No angular, muitas vezes a complexidade em interagir com múltiplos serviços, estados e componentes pode crescer de forma rápida. Imagine que, em um e-commerce, você tem serviços de carrinho de compras, gerenciamento de estoque, processamento de pagamento, e por aí vai… Cada um com suas próprias lógicas e interações. A Facade Pattern surge como uma maneira de centralizar todas essas interações em um único ponto de contato, simplificando a comunicação com os componentes.

Implementando o Facade Pattern no Angular

Tá perdido? Calma que o exemplo vai te salvar! Vem comigo 👇

Imagine que estamos construindo um e-commerce e precisamos de um carrinho de compras que interaja com um sistema de estoque, calcule o total do pedido e atualize a quantidade de produtos. Sem uma facade, os componentes precisariam se conectar diretamente a vários serviços, aumentando o acoplamento e a complexidade.

  1. Vamos criar nosso CartService, responsável por gerenciar a adição e remoção de produtos no carrinho.
@Injectable({
  providedIn: 'root',
})
export class CartService {
  private cartItems: CartItem[] = [];

  getCartItems(): CartItem[] {
    return this.cartItems;
  }

  addProductToCart(product: Product, quantity: number): void {
    // lógica para adicionar ao carrinho
    this.cartItems.push({ ...product, quantity });
  }

  removeProductFromCart(productId: string): void {
    this.cartItems = this.cartItems.filter(item => item.id !== productId);
  }

  clearCart(): void {
    this.cartItems = [];
  }
}

Enter fullscreen mode Exit fullscreen mode
  1. Vamos criar InventoryService que vai simular a disponibilidade do produto no estoque antes de adicionar ao carrinho.
@Injectable({
  providedIn: 'root',
})
export class InventoryService {
  checkProductAvailability(productId: string, quantity: number): Observable<boolean> {
    // Aqui aconteceria a verificação de disponibilidade no estoque
    return of(true);
  }
}

Enter fullscreen mode Exit fullscreen mode
  1. Por último o PaymentService processa o pagamento e finaliza a compra.
@Injectable({
  providedIn: 'root',
})
export class PaymentService {
  processPayment(totalAmount: number): Observable<string> {
    // Aqui aconteceria a logica de pagamento
    return of('Payment successful');
  }
}

Enter fullscreen mode Exit fullscreen mode

Antes de começarmos é importante que você veja como seria o nosso componente sem o Facade.

CartComponent (Sem Facade)

import { Component, OnInit } from '@angular/core';
import { CartService } from '../services/cart.service';
import { InventoryService } from '../services/inventory.service';
import { PaymentService } from '../services/payment.service';
import { CartItem } from '../models/cart-item.model';
import { Product } from '../models/product.model';
import { catchError, of } from 'rxjs';

@Component({
  selector: 'app-cart',
  templateUrl: './cart.component.html',
})
export class CartComponent implements OnInit {
  cartItems: CartItem[] = [];
  message = '';

  constructor(
    private cartService: CartService,
    private inventoryService: InventoryService,
    private paymentService: PaymentService
  ) {}

  ngOnInit(): void {
    this.cartItems = this.cartService.getCartItems();
  }

  addProduct(product: Product, quantity: number): void {
    // Checa disponibilidade no estoque antes de adicionar ao carrinho
    this.inventoryService.checkProductAvailability(product.id, quantity).subscribe((isAvailable) => {
      if (isAvailable) {
        this.cartService.addProductToCart(product, quantity);
        this.message = 'Produto adicionado ao carrinho com sucesso!';
        this.cartItems = this.cartService.getCartItems(); // Atualiza o carrinho
      } else {
        this.message = 'Produto fora de estoque';
      }
    });
  }

  removeProduct(productId: string): void {
    this.cartService.removeProductFromCart(productId);
    // Atualiza o carrinho
    this.cartItems = this.cartService.getCartItems(); 
  }

  checkout(): void {
    const totalAmount = this.calculateTotal();

    // Processo de pagamento
    this.paymentService.processPayment(totalAmount).pipe(
      catchError(() => {
        this.message = 'Falha no pagamento. Tente novamente.';
        return of(null); 
      })
    ).subscribe((result) => {
      if (result) {
        this.cartService.clearCart();
        this.message = result;
        this.cartItems = this.cartService.getCartItems(); // Limpa o carrinho
      }
    });
  }

  private calculateTotal(): number {
    return this.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Complexidade: Todo o código de verificação de disponibilidade, adição ao carrinho, e pagamento agora está diretamente no componente, tornando-o mais difícil de ler e entender.
  • Dificil manter: Qualquer mudança na lógica de estoque, adição ao carrinho, ou checkout exigirá alterações no componente, tornando a manutenção mais complexa.
  • Dificuldade nos Testes: Testar este componente se torna mais difícil, pois ele interage diretamente com múltiplos serviços. Isso requer a criação de mocks para cada serviço, e o gerenciamento de múltiplas assinaturas dos Observables.

Migrando agora para o Facade Pattern.

  1. Devemos criar o CartFacade, que será responsável pela interação entre os serviços listados acima.
import { Injectable } from '@angular/core';
import { CartService } from './cart.service';
import { InventoryService } from './inventory.service';
import { PaymentService } from './payment.service';
import { CartItem } from '../models/cart-item.model';
import { Product } from '../models/product.model';
import { Observable, of } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class CartFacade {
  constructor(
    private cartService: CartService,
    private inventoryService: InventoryService,
    private paymentService: PaymentService
  ) {}

  getCartItems(): CartItem[] {
    return this.cartService.getCartItems();
  }

  addProductToCart(product: Product, quantity: number): Observable<string> {
    // Primeiro verifica a disponibilidade no estoque
    return this.inventoryService.checkProductAvailability(product.id, quantity).pipe(
      switchMap((isAvailable) => {
        if (isAvailable) {
          this.cartService.addProductToCart(product, quantity);
          return of('Produto adicionado ao carrinho com sucesso!');
        } else {
          return of('Produto fora de estoque');
        }
      })
    );
  }

  removeProductFromCart(productId: string): void {
    this.cartService.removeProductFromCart(productId);
  }

  checkout(): Observable<string> {
    const totalAmount = this.calculateTotal();

    return this.paymentService.processPayment(totalAmount).pipe(
      switchMap((result) => {
        this.cartService.clearCart();
        return of(result);
      }),
      catchError(() => of('Falha no pagamento. Tente novamente.'))
    );
  }

  private calculateTotal(): number {
    const cartItems = this.getCartItems();
    return cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
  }
}

Enter fullscreen mode Exit fullscreen mode

Aqui, o CartFacade faz o seguinte:

  • Adiciona um produto ao carrinho: Antes de adicionar, verifica se o produto está disponível no estoque.
  • Remove um produto do carrinho: Remove diretamente através do CartService.
  • Realiza o checkout: Calcula o total e utiliza o PaymentService para processar o pagamento.

CartComponent com Facade:

import { Component, OnInit } from '@angular/core';
import { CartFacade } from '../facades/cart-facade.service';
import { Product } from '../models/product.model';

@Component({
  selector: 'app-cart',
  templateUrl: './cart.component.html',
})
export class CartComponent{
  cartItems = this.cartFacade.getCartItems();
  message: string = '';

  constructor(private cartFacade: CartFacade) {}

  addProduct(product: Product, quantity: number): void {
    this.cartFacade.addProductToCart(product, quantity).subscribe((message) => {
      this.message = message;
    });
  }

  removeProduct(productId: string): void {
    this.cartFacade.removeProductFromCart(productId);
  }

  checkout(): void {
    this.cartFacade.checkout().subscribe((result) => {
      this.message = result;
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

Note: Com esse padrão, o componente CartComponent não precisa lidar com a lógica complexa de estoque, pagamento ou estado do carrinho. Ele apenas chama métodos simples expostos pela CartFacade, que oculta todas as interações com os diferentes serviços.

Conclusão

O Facade Pattern é uma abordagem muito poderosa para simplificar a estrutura do código em aplicações Angular, dependendo da complexidade da sua aplicação, o uso de Facades atenderá totalmente suas necessidades. Mas caso seja necessário gerenciar um alto volume de dados em situações complexas, considere o uso de uma biblioteca externa, como NgRx. Você pode usar Facades em conjunto com o NgRx.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay