Dart Service: Server Application Framework

Í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.dartApplicationChannel — . 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.



All Articles