Flutter — Construindo um Projeto Boilerplate perfeito do zero.

Dec 06 2022
Crédito: Nguyễn Thành Minh (Desenvolvedor Android) Anteriormente, compartilhei cerca de 5 pontos problemáticos frequentemente experimentados no passado. Se você ainda não leu, deveria dar uma lida antes de ler isso.

Crédito: Nguyễn Thành Minh (Desenvolvedor Android)

Anteriormente, compartilhei cerca de 5 pontos problemáticos frequentemente experimentados no passado. Se você ainda não leu, deveria dar uma lida antes de ler isso.

Flutter — Construindo um Projeto Boilerplate perfeito do zero Flutter — Construindo um Projeto Boilerplate perfeito do zero. Parte 2: Ponto de dor

Veja o Projeto Boilerplate no Github

Desta vez, falarei sobre como você pode se livrar desses 5 pontos problemáticos com algo chamado Arquitetura Limpa. Antes de nos aprofundarmos nisso, precisamos entender alguns conceitos relevantes.

1. Conceitos

1.1. Regras de negócios corporativos versus regras de negócios de aplicativos

  • Enterprise Business Rules são regras relacionadas ao lado comercial de um campo. Existe em um negócio antes de automatizá-lo. Por exemplo, no campo bancário e financeiro, temos regras relevantes, como idade, renda e garantias, às quais nos referimos como regra de negócios da empresa.
  • Regras de Negócios de Aplicativos são a lógica do software, podem conter Regras de Negócios Corporativos. Um aplicativo bancário, por exemplo, poderia ter: o processo de login, etc.

Na Arquitetura Limpa, haverá 2 classes desconhecidas chamadas classe Entidade e classe Caso de Uso.

A classe Entity contém Enterprise Business Rules. Suponha que temos uma Userentidade como esta:

class User {
  User(this.income, this.age);

  final double income;
  final int age;

  bool couldBorrowMoney() {
    return age >= 18 && income >= 1000;
  }
}

1.3. Classes de caso de uso

Um caso de uso representa uma lógica de negócios do aplicativo do projeto. Ele pode conter um fluxo processando a lógica de obter a entrada da exibição e produzir a saída a ser exibida na exibição. Aqui está um exemplo para o fluxo de empréstimo de dinheiro do usuário:

import 'package:data/user_repository.dart';

class BorrowMoneyUseCase {
  const BorrowMoneyUseCase(this._userRepository);

  final UserRepository _userRepository;

  Future<BorrowMoneyStatus> execute(double money) async {
    final currentUser = await _userRepository.getMe();

    if (currentUser == null) {
      return BorrowMoneyStatus.userNotAuthorizedError;
    }

    if (!currentUser.couldBorrowMoney()) {
      return BorrowMoneyStatus.couldNotBorrowError;
    }

    await _userRepository.borrowMoney(money);

    return BorrowMoneyStatus.success;
  }
}

enum BorrowMoneyStatus {
  success,
  couldNotBorrowError,
  userNotAuthorizedError,
}

1.4. Dependência

A classe A depende da classe B quando a classe A importa a classe B.

Usando o mesmo código acima, podemos dizer que a classe BorrowMoneyUseCaseé dependente da classe UserRepository.

Da mesma forma, quando o módulo B é adicionado ao arquivo pubspec.yaml do módulo A, podemos dizer que o módulo A depende do módulo B.

2. A arquitetura de 3 módulos

Normalmente, a Clean Architecture consiste em 3 módulos principais: dados, domínio e apresentação (neste projeto, chamei-os de aplicativo de módulo em vez de apresentação).

3 módulos
  • O módulo de apresentação contém classes UI e Bloco. Sua finalidade é receber eventos do usuário e executar os casos de uso de acordo para enviar de volta os dados que precisam ser exibidos na interface do usuário.
  • O módulo Domain contém as classes Entity e Use Cases. Em outras palavras, este é o módulo que contém a lógica de negócios do aplicativo.
  • O módulo de dados contém repositórios para receber dados de casos de uso, bem como fornecer dados para casos de uso. A fonte de dados pode vir de diferentes servidores por meio de API, banco de dados local ou preferência compartilhada, etc.

3. Regra de Dependência

Esta regra pode ser entendida assim:

Módulos instáveis ​​devem depender de módulos estáveis.

Módulos instáveis ​​provavelmente serão alterados repetidamente. Pegue o aplicativo do Facebook, por exemplo, seus arquivos de interface do usuário e bancos de dados locais são coisas que o desenvolvedor do Facebook muda a cada semana ou mês. Dessa forma, os módulos Apresentação e Dados são módulos instáveis, deixando o Domínio como o mais estável. É claro porque o módulo Domínio contém Regras de Negócios que dificilmente são alteradas.

Resumindo, a relação entre os 3 módulos é a seguinte: os módulos Apresentação e Dados vão depender do módulo Domínio.

regra de dependência

Como fazer o módulo Domínio não depender do módulo Dados quando classes Use Case precisam importar classes Repositório? É aí que a técnica de Inversão de Dependência entra em ação.

4. Inversão de Dependência

O problema atual é que o módulo Domínio agora depende do módulo Dados porque a BorrowMoneyUseCaseclasse precisa importar a UserRepositoryclasse.

Para reverter essa dependência, precisamos criar um Repositório de Interface ou Classe Abstrata no módulo Domínio, e no lado do módulo Dados, criaremos classes de Implementação do Repositório que herdarão esse Repositório de Interface/Classe Abstrato. Então, as classes de caso de uso precisarão apenas importar o Repositório de Classes Interface/Abstract.

Inversão de Dependência

Assim, o módulo Domínio não depende mais do módulo Dados. Ambos agora dependem da abstração. Essa também é a declaração do princípio de Inversão de Dependência nos 5 princípios do SOLID.

Depois de entender a regra de dependência, precisamos ajustar o código. O primeiro passo será criar uma classe abstrata UserRepository.

import '../user.dart';

abstract class UserRepository {
  Future<User?> getMe();  

  Future<void> borrowMoney(double money);
}

import '../user_repository.dart';

class BorrowMoneyUseCase {
  const BorrowMoneyUseCase(this._userRepository);

  final UserRepository _userRepository;

  Future<BorrowMoneyStatus> execute(double money) async {
    final currentUser = await _userRepository.getMe();

    if (currentUser == null) {
      return BorrowMoneyStatus.userNotAuthorizedError;
    }

    if (!currentUser.couldBorrowMoney()) {
      return BorrowMoneyStatus.couldNotBorrowError;
    }

    await _userRepository.borrowMoney(money);

    return BorrowMoneyStatus.success;
  }
}

import 'package:domain/user_repository.dart';
import 'package:domain/user.dart';

class UserRepositoryImpl implements UserRepository {
  @override
  Future<User?> getMe() {
    // TODO
  }

  @override
  Future<void> borrowMoney(double money) {
    // TODO
  }
}

Conclusão

O modelo de 3 módulos é uma das características da Clean Architecture. Ainda tem muitas características excelentes e emocionantes. Na próxima parte, aprenderemos mais sobre ele e veremos como ele realmente é lindo.