Índice
Treinamento
Da última vez que terminamos, colocamos um manequim de página da Web estática, desenvolvido usando Flutter para a web. A página exibe o andamento do desenvolvimento do nosso serviço, mas os dados nas datas de início do desenvolvimento e lançamento tiveram que ser codificados no aplicativo. Assim, perdemos a capacidade de alterar as informações na página. É hora de desenvolver um aplicativo de servidor de dados. Um diagrama de todos os aplicativos de serviço está no artigo "Serviço em linguagem Dart: Introdução, infraestrutura de back-end" .
Neste artigo, vamos escrever um aplicativo usando a estrutura Aqueduct, avalie seu desempenho e consumo de recursos em diferentes modos, escreva um kit de ferramentas para compilação em um aplicativo nativo para Windows e Linux, lide com migrações do esquema de banco de dados para classes de aplicativo de domínio e até mesmo publique nossa imagem docker de ferramentas no registro DockerHub público.

Utilidade
Instalando Aqueduto
Vamos começar instalando o dart-sdk, um conjunto de ferramentas de desenvolvimento Dart. Você pode instalá-lo usando o gerenciador de pacotes do seu sistema operacional, conforme sugerido aqui . No entanto, no caso do Windows, nenhum gerenciador de pacotes é instalado em seu sistema por padrão. Então apenas:
- Baixe o arquivo e descompacte-o na unidade C:
- , , , . . « »
- Path . dart , , C:\dart-sdk\bin
- , dart pub ( dart)
dart --version
pub -v
- , ,
- aqueduct CLI (command line interface)
pub global activate aqueduct
aqueduct
Em teoria, um servidor de banco de dados PostgreSQL também poderia ser instalado localmente . No entanto, o Docker nos permitirá evitar essa necessidade e tornar o ambiente de desenvolvimento semelhante ao tempo de execução no servidor.
Geração de aplicativos
Então, vamos abrir nossa pasta de servidor em VsCode
code c:/docs/dart_server
Para quem não viu o primeiro e o segundo artigos, o código-fonte pode ser clonado do repositório guthub :
git clone https://github.com/AndX2/dart_server.git
Vamos criar um modelo de aplicativo:
aqueduct create data_app

Vamos nos familiarizar com o conteúdo do modelo de projeto:
- README.md - uma nota que descreve como trabalhar com um projeto aqueduct, executar testes, gerar documentação de API, etc. Arquivo de suporte.
- pubspec.yaml — pub. , , , .
- config.yaml config.src.yaml — . .
- analysis_options.yaml — ( ). .
- .travis.yml — (continuous Integration). .
- pubspec.lock .packages — pub. — , , — ().
- .dart_tool/package_config.json — , aqueduct CLI. .
- bin/main.dart — (, ). ( ).
- lib/channel.dart — ApplicationChannel — . Aqueduct CPU RAM. ( Dart isolate) () .
- lib/data_app.dart — . (library) dart_app
- test/ — . -, . Postman.
Configuração
A primeira tarefa a ser resolvida é configurar o aplicativo na inicialização. O Aqueduct tem um mecanismo integrado para extrair parâmetros de arquivos de configuração, mas esse mecanismo não é muito conveniente quando executado em um contêiner do Docker. Vamos agir de forma diferente:
- Vamos passar a lista de variáveis para o sistema operacional do contêiner.
- Ao iniciar o aplicativo dentro do contêiner, leremos as variáveis de ambiente do sistema operacional e as usaremos para a configuração inicial.
- Vamos criar uma rota para visualizar todas as variáveis de ambiente do aplicativo em execução na rede (isso será útil ao visualizar o estado do aplicativo no painel de administração).
Na pasta / lib , crie várias pastas e o primeiro repositório para acessar variáveis de ambiente:

EnvironmentRepository no construtor lê as variáveis de ambiente do sistema operacional na forma de um dicionário Map <String, String> e as salva na variável privada _env . Vamos adicionar um método para obter todos os parâmetros na forma de um dicionário: lib / service / EnvironmentService - o componente lógico para acessar os dados EnvironmentRepository:


Injeção de dependência
Aqui você precisa parar e lidar com as dependências dos componentes:
- o controlador de rede precisará de uma instância do serviço variável,
- o serviço deve ser exclusivo para todo o aplicativo,
- para criar um serviço, você deve primeiro criar uma instância do repositório de variáveis.
Vamos resolver esses problemas usando a biblioteca GetIt . Conecte o pacote necessário a pubspec.yaml :

Crie uma instância do contêiner injetor lib / di / di_container.dart e escreva um método com o registro do repositório e serviço: Chame o método de inicialização do contêiner DI no método de preparação do aplicativo:


Camada de rede
lib / controller / ActuatorController - componente de rede http. Ele contém métodos para acessar os dados de serviço do aplicativo: Vamos declarar manipuladores de rota para controladores em lib / controller / Routes :


Primeira partida
Para correr, você precisa de:
- empacote o aplicativo em uma imagem Docker,
- adicionar contêiner ao script docker-compose,
- configurar NGINX para solicitações de proxy.
Crie um Dockerfile na pasta do aplicativo . Este é o script para construir e executar a imagem do Docker: Adicione o contêiner do aplicativo ao script docker-compose.yaml : Crie o arquivo data_app.env com variáveis de configuração para o aplicativo: Adicione um novo local ao NGINX debug config conf.dev.d / default.conf : Execute o debug script com o sinalizador de pré-construção:




docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml up --build

O script foi executado com sucesso, mas existem vários pontos alarmantes:
- a imagem oficial do dardo do google tem 290 MB arquivada . Quando descompactado, ele ocupará muito mais espaço - 754 MB. Veja uma lista de imagens e seus tamanhos:
docker images
- O tempo de construção e compilação JIT foi de mais de 100 segundos. Demais para executar um aplicativo em promoção
- Consumo de memória no painel docker 300 MB imediatamente após o lançamento
- Em um teste de carga (rede GET / api / atuador / solicitações apenas), o consumo de memória está na faixa de 350-390 MB para um aplicativo em execução em um isolado
Presumivelmente, os recursos de nosso VPS de orçamento não são suficientes para executar um aplicativo com tantos recursos. Vamos checar:
- Crie uma pasta no servidor para a nova versão do aplicativo e copie o conteúdo do projeto
ssh root@dartservice.ru "mkdir -p /opt/srv_2" && scp -r ./* root@91.230.60.120:/opt/srv_2/
- Agora você precisa transferir para esta pasta o projeto da página da web de / opt / srv_1 / public / e todo o conteúdo da pasta / opt / srv_1 / sertbot / (contém certificados SSL para NGINX e vamos criptografar logs de bot), copie também a chave de / opt / srv_1 / dhparam /
- Inicie o monitor de recursos do servidor em um console separado
htop
- Vamos executar o script docker-compose na pasta / opt / srv_2 /
docker-compose up --build -d
- Esta é a aparência do assembly do aplicativo antes do lançamento:
- E assim - no trabalho:
do 1 GB de RAM disponível, nosso aplicativo consome 1,5 GB "ocupando" o arquivo de paginação ausente. Sim, o aplicativo foi iniciado, mas não estamos falando sobre nenhuma capacidade de carga. - Vamos parar o script:
docker-compose down
AOT
Temos três tarefas a resolver:
- reduzir o consumo de RAM por aplicativo de dardo,
- reduza o tempo de inicialização,
- reduza o tamanho do contêiner do docker do aplicativo.
A solução é abandonar o dardo em tempo de execução. Desde a versão 2.6, os aplicativos dart suportam compilação em código executável nativo . Aqueduct oferece suporte à compilação a partir da versão 4.0.0-b1.
Vamos começar removendo a CLI do aqueduto localmente:
pub global deactivate aqueduct
Instale a nova versão:
pub global activate aqueduct 4.0.0-b1
Vamos aumentar as dependências em pubspec.yaml: Vamos construir o

aplicativo nativo:
aqueduct build
O resultado será um único arquivo data_app.aot assembly de cerca de 6 MB de tamanho. Você pode iniciar imediatamente este aplicativo com parâmetros, por exemplo:
data_app.aot --port 8080 --isolates 2
O consumo de memória imediatamente após o lançamento é inferior a 10 MB.
Vamos ver sob carga. Parâmetros de teste: solicitações GET / atuador da rede, 100 threads com velocidade máxima disponível, 10 minutos. Resultado:

Total: velocidade média - 13k solicitações por segundo para um corpo de resposta JSON de 1,4kV, tempo médio de resposta - 7 ms, consumo de memória (para duas instâncias) 42 MB. Não há erros.
Quando repetimos o teste com seis instâncias do aplicativo, a velocidade média, claro, sobe para 19k / s, mas a utilização do processador chega a 45% quando o consumo de memória é de 64 MB.
Este é um excelente resultado.
Embalagem de container
Aqui enfrentaremos mais uma dificuldade: só podemos compilar um aplicativo DART em um nativo para o sistema operacional atual. No meu caso, este é o Windows10 x64. Em um contêiner docker, eu certamente preferiria uma das distribuições Linux - por exemplo, Ubuntu 20.10.
A solução aqui será um docker-stand intermediário, usado apenas para construir aplicativos nativos para o Ubuntu. Vamos escrever / dart2native / Dockerfile : Agora vamos construí-lo em uma imagem docker chamada aqueduct_builder: 4.0.0-b1 , sobrescrevendo as versões antigas, se houver:

docker build --pull --rm -f "dart2native\Dockerfile" -t aqueduct_builder:4.0.0-b1 "dart2native"
Vamos checar:
docker images

Vamos escrever um script de construção para o aplicativo nativo docker-compose.dev.build.yaml : Execute o script de construção:

docker-compose -f docker-compose.dev.build.yaml up

O arquivo data_app.aot compilado para o Ubuntu já ocupa 9 MB. Na inicialização, utiliza 19 MB de RAM (para duas instâncias). Vamos realizar o teste de carga local com as mesmas condições, mas em um contêiner com proxy NGINX (GET, 100 threads):

Em média, 5,3 mil solicitações por segundo. Ao mesmo tempo, o consumo de RAM não excedeu 55 MB. O tamanho da imagem diminuiu em comparação com o dardo e aqueduto instalados de 840 MB para 74 MB no disco.
Vamos escrever um novo script docker-compose.aot.yaml para iniciar o aplicativo. Para fazer isso, vamos substituir o bloco de descrição data_app instalando a imagem de base “vazia” do Ubuntu: 20.10. Vamos montar o arquivo de montagem e alterar o comando de inicialização:

Vamos resolver mais um problema de serviço: na verdade, a imagem de construção do docker com dardo e aqueduto instalados é uma ferramenta bastante reutilizável. Faz sentido carregá-lo em um registro público e conectá-lo como uma imagem pronta para download. Isto exige:
- registrar-se com um registro público, como DockerHub ,
- faça login localmente com o mesmo login
docker login
- renomeie a imagem enviada usando o esquema login / title: tag
docker image tag a365ac7f5bbb andx2/aqueduct:4.0.0-b1
- descarregar imagem no registro
docker push andx2/aqueduct:4.0.0-b1
https://hub.docker.com/repository/docker/andx2/aqueduct/general
Agora podemos modificar nosso script de construção para usar a imagem pública

Conexão de banco de dados
O Aqueduct já possui um ORM embutido para trabalhar com um banco de dados PostgreSQL. Para usá-lo, você precisa:
- Crie objetos de domínio que descrevem registros no banco de dados.
- . : , , . Aqueduct , , ManagedObject ( ), , . .
- . , , .
- , aqueduct, , seed() — - .
- aqueduct CLI.
Vamos começar conectando um novo contêiner docker ao banco de dados PostgreSQL no script docker-compose.aot.yaml. Imagem pronta baseada em Linux Alpine (versão "compacta" do Linux para aplicativos embutidos): Aqui você precisa prestar atenção ao arquivo de variável de ambiente data_db.env . O fato é que a imagem é pré-configurada para usar essas variáveis como nome de usuário, host, porta e senha de acesso. Vamos adicionar essas variáveis ao arquivo: Os valores são fornecidos condicionalmente. Também montaremos a pasta host ./data_db/ em um contêiner para armazenar dados do banco de dados. Em seguida, no aplicativo data_app, adicione a classe / service / DbHelper para se conectar ao banco de dados usando variáveis de ambiente:



Vamos criar um objeto de domínio gerenciado por ORM para obter as configurações do aplicativo cliente: Adicione um repositório e serviço para adicionar configurações e obter a versão atual: Controlador de rede: Registre novos componentes no contêiner DI: Adicione um novo controlador e ponto de extremidade ao roteador: Agora geramos um arquivo de migração de banco de dados. Vamos executar:






aqueduct db generate
O resultado será a criação de arquivos de migração na pasta do projeto:
Agora você precisa resolver o problema do serviço: as migrações devem ser aplicadas de um sistema com aqueduto (e dart) instalado para um banco de dados rodando em um contêiner, e isso deve ser feito durante o desenvolvimento local e no servidor. Para este caso, usaremos a imagem previamente criada e publicada para a montagem AOT. Vamos escrever o script de migração do banco de dados docker-compose correspondente:
Um detalhe interessante é a string de conexão do banco de dados. Ao executar o script, você pode passar um arquivo com variáveis de ambiente como um argumento e, em seguida, usar essas variáveis para substituição no script:



docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env --compatibility up --abort-on-container-exit
Também vamos prestar atenção aos sinalizadores de lançamento:
- --compatibility - compatibilidade com versões docker-compose de scripts 2.x. Isso permitirá que as opções de implantação sejam usadas para restringir o uso de recursos pelo contêiner, que são ignorados nas versões 3.x. Limitamos o consumo de RAM a 200 MB e o uso da CPU a 50%
- --abort-on-container-exit - Este sinalizador define o modo de execução do script para que quando um dos contêineres de script parar, todos os outros serão encerrados. Portanto, quando o comando para migrar o esquema do banco de dados é executado e o contêiner com aqueduto é interrompido, docker-compose também encerrará o contêiner do banco de dados.
Publicação
Para se preparar para a publicação do aplicativo, você deve:
- data_app.env data_db.env. , POSTGRES_PASSWORD=postgres_password
- docker-compose.aot.yaml docker-compose.yaml.
- /api/actuator. .
Copie a pasta do aplicativo ./data_app/ para o servidor . Um ponto importante aqui será a opção -p (copiar enquanto preserva os atributos do arquivo) no comando de cópia. Deixe-me lembrá-lo de que, ao construir o aplicativo nativo, definimos os direitos de execução para o arquivo data_app.aot :
scp -rp ./data_app root@dartservice.ru:/opt/srv_1
Vamos também copiar:
- Configuração NGINX alterada ./conf.d/default.conf
- Scripts de inicialização e migração docker-compose.yaml , docker-compose.migrations.yaml
- Arquivos com variáveis de ambiente data_app.env e data_db.env
Adicione a pasta / opt / srv_1 / data_db no servidor . Este é o volume do sistema de arquivos do host a ser montado no contêiner do banco de dados. Todos os dados PostgreSQL serão salvos aqui.
mkdir /opt/srv_2/data_db
Vamos executar o script para migrar o esquema do banco de dados:
docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env up --abort-on-container-exit
Vamos executar o script do aplicativo:
docker-compose up -d
-> código fonte github
Em vez de uma conclusão
A estrutura para os aplicativos de back-end está pronta. No próximo artigo, com base nele, escreveremos um novo aplicativo para autorização de usuários do serviço. Para fazer isso, vamos usar a especificação oAuth2 e integrar com VK e Github.