Repositório para versionamento e documentação básica do projeto Fluxo de Caixa no GitHub.
Autor | Última alteração |
---|---|
Flávio dos Santos | 14 Maio de 2023 |
Este é um repositório para mostrar a implementação e o funcionamento de uma aplicação do tipo Web API onde um comerciante precisa controlar seu fluxo de caixa diário com os lançamentos (débitos e créditos) como também um relatório que disponibilize o saldo consolidado.
Por questões de tempo, os testes unitários foram implementados na classe Categoria Service e no momento estou implementando o mesmo na classe Cliente Service.
Nessa primeira versão, para fins didáticos e também como utilizei apenas do meu tempo livre disponível, o relatório será apenas um retorno em JSON, mas em futuro próximo estarei implemetando o retorno de um arquivo PDF representando o relatório.
- C#;
- Fluent Validation;
- Auto Mapper;
- Dapper;
- PostgreSQL;
- EF .NET Core 7;
- Visual Studio 2022;
- Docker;
- Linux Ubuntu Server;
- Swagger.
- Application
- A camada application Tem a função de receber todas as requisições http e direcioná-las para a camada business para aplicar as validações e regras de negócio.
- Domain
- É a área de definição dos modelos, entidades, DTOs e Interfaces.
- Business
- Na business, concentramos toda a regra de negócio do domínio.
- Infrastructure
- Dividida em duas subcamadas, o Data, onde são realziadas as persistênciasno banco de dados, utilizando ou não algum ORM e a camada Cross-Cutting, uma camada destinada a ser utilizada para consumo de API externas.
Nosso comerciante fictício irá consumir nossa API hospedada em um modelo colocation em um plano da AWS, o Amazon Lightsail que poucos conhecem, onde o cliente paga um valor fixo por EC2 contratado semelhante as hospedagens tradicionais.
O NGINX irá atuar como proxy reverso e nosso load-balance uma vez que ele cumpre bem o papel de balanceamento de carga trazendo mais performance para nossa aplicação.
Abaixo, segue arquivo nginx.conf do NGINX com a configuração do proxy reverso e load balance:
events{}
http{
include mime.types;
default_type 'application/json';
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Content-Type' 'application/json';
add_header 'Content-Type' 'application/x-www-form-urlencoded';
add_header 'Content-Type' 'application/form-data';
client_max_body_size 200M;
upstream fluxocaixa {
server api.financeiro.com.br:81;
server api.financeiro.com.br:82;
server api.financeiro.com.br:83;
}
server {
listen 80;
#server_name app.financeiro.com.br;
client_max_body_size 200M;
server_name 127.0.0.1;
gzip on;
# ----------------------------------------------------------------------
# 1 - endpoints da Empresa Financeiro --> http://app.financeiro.com.br/fluxocaixa/
# ----------------------------------------------------------------------
# 1.1 - Cadastrar um Cliente
location /fluxocaixa/api/v1/Cliente {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Cliente/;
}
# 1.2 - Listar Clientes
location /fluxocaixa/api/v1/Cliente {
proxy_method GET;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Cliente/;
}
# 1.3 - Cadastrar um Fornecedor
location /fluxocaixa/api/v1/Fornecedor {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Fornecedor/;
}
# 1.4 - Listar Fornecedores
location /fluxocaixa/api/v1/Fornecedor {
proxy_method GET;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Fornecedor/;
}
# 1.5 - Cadastrar uma Categoria
location /fluxocaixa/api/v1/Categoria {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Categoria/;
}
# 1.6 - Listar Categorias
location /fluxocaixa/api/v1/Categoria {
proxy_method GET;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Categoria/;
}
# 1.7 - Remover uma Categoria
location /fluxocaixa/api/v1/Categoria {
proxy_method DELETE;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Categoria/;
}
# 1.9 - Abrir Caixa
location /fluxocaixa/api/v1/Caixa/Abrir {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Caixa/Abrir/;
}
# 1.10 - Fechar Caixa
location /fluxocaixa/api/v1/Caixa/Fechar {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Caixa/Fechar/;
}
# 1.11 - Receber
location /fluxocaixa/api/v1/Caixa/Receber {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Caixa/Receber/;
}
# 1.12 - Pagar
location /fluxocaixa/api/v1/Caixa/Pagar {
proxy_method POST;
proxy_set_header content-type "application/json";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_pass http://fluxocaixa/api/v1/Caixa/Pagar/;
}
}
}
Após a configuração do arquivo nginx.conf, basta reiniciar o NGINX para que as alterações no arquivo conf tenha efeito:
sudo service nginx restart
Em nosso caso, não vamos utilizar o docker file minimalista, onde iremos disponibilizar a build de nossa API em uma pasta específica para que o docker apenas copie a mesma para dentro do container:
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app/data
WORKDIR /app
EXPOSE 80
EXPOSE 443
COPY /build /app
ENTRYPOINT ["dotnet", "Financeiro.FluxoCaixa.dll"]
Agora vamos configurar os 3(três) arquivos onde iremos subir nossas builds e com isso podemos atualizá-las de forma incremental uma a uma sem deixar nossa API indisponível. O primeiro arquivo iremos subir nossa aplicação na porta 81:
# -- Lista as imagens Ativas e Inativas
# docker ps -a
# docker-compose -f docker-compose.yaml up -d
# docker-compose -f docker-compose.yaml down --rmi local
version: "3.8"
# ------- serviços -----------------------------
services:
api:
container_name: api-fluxocaixa
ports:
- "81:80"
build:
context: .
dockerfile: Dockerfile
volumes:
- /mnt/arquivos:/app/data
restart: always
O segundo arquivo, iremos subir nossa aplicação na porta 82:
# -- Lista as imagens Ativas e Inativas
# docker ps -a
# docker-compose -f docker-compose.yaml up -d
# docker-compose -f docker-compose.yaml down --rmi local
version: "3.8"
# ------- serviços -----------------------------
services:
api:
container_name: api-fluxocaixa
ports:
- "82:80"
build:
context: .
dockerfile: Dockerfile
volumes:
- /mnt/arquivos:/app/data
restart: always
E o terceiro e último arquivo, iremos subir nossa aplicação na porta 83:
# -- Lista as imagens Ativas e Inativas
# docker ps -a
# docker-compose -f docker-compose.yaml up -d
# docker-compose -f docker-compose.yaml down --rmi local
version: "3.8"
# ------- serviços -----------------------------
services:
api:
container_name: api-fluxocaixa
ports:
- "83:80"
build:
context: .
dockerfile: Dockerfile
volumes:
- /mnt/arquivos:/app/data
restart: always
Para subir os contaners, basta executar o comando abaixo:
docker-compose -f docker-compose.yaml up -d
E caso queira parar um container específico para realizar atualização:
docker-compose -f docker-compose.yaml down --rmi local
É importante lembrar que deve-se criar uma pasta para cada instância de container que deseja subir, podendo até ser api81, api82 e api83. E dentro dessas pastas os arquivos, Dockerfile, docker-compose.yaml e a pasta build com a release do projeto .NET. Os comandos de subida e stop dos container devem ser aplicados dentro de cada respectivas pastas/diretórios.
O SGBD que estamos utilizando nesse projeto é o PostgreSQL e o mesmo estava instalado instalado em um servidor Linux Ubuntu Server, mas nada impede que seja instalado localmente em uma maquina Windows 10 Desktop. Para a criação do banco e a a execução dos seus respectivos scripts DDL, utilizamos a ferramenta Pg Admin 4.
Na Entidade Pessoa para fins didáticos, não estamos utilizando o campo CPF/CNPJ para tornar a identificação forte sem complicar o processo ditático, e no lugar estamos utilizando o HasNome para otimizar o índice da tabela.
Porém eu fiz uma pesquisa rápida em alguns comércios pequenos como Oficina Mecânica, e detectei que é comum o estabelecimento comercial não solicitar o número do CPF/CNPJ ao cliente.
Scripts necessários para a criação do banco de dados esuas respecitvas tabelas:
CREATE DATABASE financeiro TEMPLATE = template0 LC_CTYPE = "pt_BR.UTF-8" LC_COLLATE = "pt_BR.UTF-8";
ou
CREATE DATABASE financeiro TEMPLATE=template0 LC_CTYPE="Portuguese_Brazil.1252" LC_COLLATE="Portuguese_Brazil.1252";
CREATE TABLE IF NOT EXISTS public.categoria
(
id BIGSERIAL NOT NULL,
nome CHARACTER VARYING(50) NOT NULL,
tipo CHAR(1) NOT NULL,
CONSTRAINT pk_categoria PRIMARY KEY(id)
);
COMMENT ON TABLE public.categoria IS 'Categoria de Títulos. Tipo: E = Entrada e S = Saída';
-- Configuração default
INSERT INTO public.categoria
(
nome,
tipo
)
VALUES
(
'Entradas',
'E'
);
INSERT INTO public.categoria
(
nome,
tipo
)
VALUES
(
'Saídas',
'S'
);
INSERT INTO public.categoria
(
nome,
tipo
)
VALUES
(
'Saldo Inicial',
'I'
);
INSERT INTO public.categoria
(
nome,
tipo
)
VALUES
(
'Saldo Final',
'F'
);
CREATE TABLE IF NOT EXISTS public.pessoa
(
id BIGSERIAL NOT NULL,
nome CHARACTER VARYING(50) NOT NULL,
hash_nome CHARACTER VARYING(32) NOT NULL,
dt_inclusao TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT pk_pessoa PRIMARY KEY(id)
);
CREATE INDEX ix_pessoa_hash_nome ON public.pessoa( hash_nome );
CREATE TABLE IF NOT EXISTS public.cliente
(
id BIGSERIAL NOT NULL,
pessoa_id BIGINT NOT NULL,
dt_inclusao TIMESTAMP WITH TIME ZONE NOT NULL,
CONSTRAINT pk_cliente PRIMARY KEY(id),
CONSTRAINT fk_cliente_pessoa FOREIGN KEY (pessoa_id) REFERENCES public.pessoa(id)
);
CREATE TABLE IF NOT EXISTS public.fornecedor
(
id BIGSERIAL NOT NULL,
pessoa_id BIGINT NOT NULL,
dt_inclusao TIMESTAMP NOT NULL,
CONSTRAINT pk_fornecedor PRIMARY KEY(id),
CONSTRAINT fk_fornecedor_pessoa FOREIGN KEY (pessoa_id) REFERENCES public.pessoa(id)
);
/*
Como definimos um minúsculo escopo de registro de entradas e saídas financeiras,
resolvi adicionar o fornecedor e cliente na própria entidade de extrato, utilizando
o sua herança de entidade pessoa.
*/
CREATE TABLE IF NOT EXISTS public.extrato
(
id BIGSERIAL NOT NULL,
categoria_id BIGINT NOT NULL,
pessoa_id BIGINT NOT NULL,
tipo CHAR(1) NOT NULL,
descricao CHARACTER VARYING(50) NOT NULL,
valor DECIMAL NOT NULL,
saldo DECIMAL NOT NULL,
valor_relatorio DECIMAL NOT NULL,
dt_extrato TIMESTAMP NOT NULL,
dt_inclusao TIMESTAMP NOT NULL,
CONSTRAINT pk_extrato PRIMARY KEY(id),
CONSTRAINT fk_extrato_categoria FOREIGN KEY (categoria_id) REFERENCES public.pessoa(id),
CONSTRAINT fk_extrato_pessoa FOREIGN KEY (pessoa_id) REFERENCES public.pessoa(id)
);
COMMENT ON TABLE public.categoria IS 'Extrato - Tipo: D = Débito, C = Crédito e S = Saldo';
CREATE TABLE IF NOT EXISTS public.saldo_diario
(
id BIGSERIAL NOT NULL,
dt_saldo TIMESTAMP NOT NULL,
tipo CHAR(1) NOT NULL,
valor DECIMAL NOT NULL,
dt_inclusao TIMESTAMP NOT NULL,
extrato_id BIGINT NOT NULL,
CONSTRAINT pk_saldo_diario PRIMARY KEY(id),
CONSTRAINT fk_sado_diario_extrato FOREIGN KEY (extrato_id) REFERENCES public.extrato(id)
);
CREATE INDEX ix_saldo_diario_periodo ON public.saldo_diario( dt_saldo, tipo );
COMMENT ON TABLE public.saldo_diario IS 'Extrato - Tipo: I = Inicial e F = Final';
Para baixar a API, execute o git clone do projeto:
git clone https://github.com/flavio-santos-ti/API-Financeiro-Fluxo-Caixa.git
Para executar o projeto localmente, abra o Visual Studio 2022 e acesse a solução localizado na pasta API-Financeiro-Fluxo-Caixa\Financeiro.FluxoCaixa arquivo Financeiro.FluxoCaixa.sln. Em seguida e execute-o e o swagger será aberto com toda a documentação da API.