azk CLI: como melhoramos a central de comando do azk

"ground control to Major Tom…"

To read the English version, click here.

Estamos lançando o azk v0.14.0 e, tal como explicamos no seu artigo de lançamento aqui no blog, a refatoração da CLI (command line interface) do azk foi uma das grandes novidades desta versão.

Falaremos um pouco mais aqui sobre a motivação dessas mudanças e como as fizemos.

Motivação

Hoje, a CLI do azk é sua única interface de uso (ainda…). Ela é o centro de operações através do qual toda e qualquer tarefa é executada.

Sua performance e sua usabilidade são cruciais para o melhor aproveitamento do azk.

Embora ela não tivesse a mesma performance de execução de um grep ou um de awk, a CLI do azk não era exatamente lenta. Por outro lado, essas próprias referências já eram indicação para nós de que ainda havia espaço para melhorar a performance da CLI.

Uma redução de milésimos de segundo na execução e resposta de um único comando não faz muita diferença, mas numa única sessão de uso do azk vários comandos podem ser dados. E se quisermos que o azk realmente se torne um padrão de desenvolvimento, ele certamente será usado várias vezes por dia por um mesmo usuário.

Aqueles milésimos de segundos começam a fazer diferença nesse cenário.

Cremos que o mesmo vale para a usabilidade da CLI. Por isso tivemos cuidados especiais com a formação dos comandos, a estilização dos outputs e com os recursos de ajuda na CLI.

Todas essas coisas devem funcionar como uma segunda natureza para o nosso usuário e precisam se tornar tão automáticas para ele quanto respirar.

O desafio não foi pequeno. Em versões anteriores do azk, tínhamos optado por construir nosso próprio parser, o que resultou em alguns problemas de performance e nos levou a outra questão, como explicamos abaixo.

Padrão Unix

Embora o azk não tenha sido criado como mera interface de uso para o Docker, muitos dos seus recursos são possíveis pelo uso de containers em seu core.

Assim, por uma questão de facilidade de uso, optamos por seguir o mesmo comportamento de CLI já oferecido pelo Docker.

Isso significa que o azk interpretava as opções passadas à CLI segundo a sequência delas quando passadas a um dado comando, o que levava a alguns problemas. Por exemplo:

$ azk --log=debug start 
$ azk start --log=debug

Apesar da similaridade entre os comandos acima, eles não teriam o mesmo efeito. O primeiro iria funcionar, mas o segundo iria falhar (e avisaria que o `--log=debug` não é uma opção válida para o comando start).

No mundo Unix, o correto é suportar qualquer sequência para as opções passadas a um comando. Em nossa decisão precoce de suportar no azk um comportamento e CLI similar ao do Docker, decidimos desenvolver nosso próprio parser de comandos, mas isso tornou o trabalho de manutenção da CLI complicado.

A CLI se tornou limitada, de difícil manutenção e ainda deixou a desejar no quesito de usabilidade.

Algumas opções passadas ao comando precisavam estar na ordem ou sequência determinada, o help só poderia ser chamado de forma muito específica e era inviável gerar um “Auto Complete” (que lista as opções de comando e formatacão disponíveis enquanto se digita, quando você pressionar tab no teclado).

Soluções

Performance e usabilidade sempre serão prioridades no azk. Não adianta o azk ser “mágico” em suas funcionalidades se ele for lento ou pouco intuitivo de se usar.

Para solucionarmos os problemas listados anteriormente, vamos primeiro recapitular os requisitos. A CLI deve:

  • ser “unix standard” ou Posix;
  • ser fácil de usar;
  • ter baixo tempo de resposta;
  • ser facilmente modificável (para fins de evolução);
  • oferecer uma forma fácil de suportar "Auto Complete" no terminal.

Primeira parte: seguindo o padrão Unix

Depois de várias pesquisas encontramos uma solução que atendia aos requisitos acima e usava uma abordagem nova para um problema conhecido. Trata-se de uma ferramenta que faz o parsing das opções passadas a um comando com base no help de uso do comando.

Esta ferramenta é o Docopt.

Mais que uma simples implementação das funções acima, ele está se tornando um verdadeiro padrão de como se tratar o parsing de comandos na CLI. Além de uma rica documentação de opções de comandos Unix, ele ainda oferece implementações para diferentes linguagens, incluindo JavaScript (linguagem de implementação dessas funções no azk).

Com o Docopt, encontramos a primeira parte da nossa solução. Restava então solucionar a questão da execução de ações uma vez que o Docopt tivesse "parseado" as opções passadas aos comandos na CLI.

Segunda parte: performance e modularização

Como tínhamos implementado nosso próprio parser no azk, acabamos com um complexo sistema de classes e heranças. Se, por um lado, ele facilitava o load dos comandos e das opções suportadas pelo parser, por outro, ele era muito pesado e monolítico, o que tornava a CLI lenta e difícil evolução.

Apesar de poderoso e versátil, o Docopt não resolve a questão de endereçamento das opções "parseadas" por ele. Precisávamos então de um sistema de “rotas” que chamasse o “código” responsável por cada comando propriamente dito com base nas opções extraídas pelo Docopt.

Evitamos a tentação de modificar o Docopt e acabarmos criando um outro “monolito” e partimos para uma abordagem comum em frameworks web: um sistema de rotas e controladores.

Para ligar as opções "parseadas" pelo Docopt com as devidas ações, construímos o cli-router.

Nasce o cli-router

O cli-router nada mais é do que uma implementação simples de um router: um conceito onde uma série de regras guiam a decisão de qual ação (controller) deve ser chamada.

Você pode encontrá-lo aqui.

Com base em uma serie de filtros ou regras, o cli-router é capaz de olhar para os parâmetros e decidir quem ele deve chamar.

Vejamos um exemplo simples:

https://gist.github.com/gullitmiranda/54e5c04cd4ff5bc02018

Explicação das rotas:

  • help: se o comando for help, se a opção "--help" existir ou se o comando não possuir nenhum argumento, chama o controlador “Help”;
  • version: se o comando for version ou se a a opção "--version" existir, chama o controlador “Version”;
  • hello: como aqui não foi passado nenhum filtro, o cli-router automaticamente cria um filtro que verifica se o comando é igual ao nome da rota, que neste caso é “hello”.

E o help?

Uma das coisas de que temos orgulho no azk é o seu help.

Além de flexível, ele era construído dinamicamente com base nas opções passadas ao nosso antigo parser.

Com a mudança para o cli-router e o Docopt, abrimos mão dessa implementação e precisamos de uma nova abordagem. Graças à flexibilidade da nova abordagem, construir um novo sistema de help se revelou fácil e ainda nos permitiu criar melhorias como o sistema de “Auto Complete”.

O cli-router possui um único controlador chamado help, que é capaz de deixar o help da CLI dinâmico. Por exemplo: caso você chame o help de um comando específico, ele extrai as informações pertinentes e retorna um novo help.

Esse controlador foi desenvolvido para ser extensível. Assim é possível aplicar alterações e estilizações específicas para cada aplicação. No azk, por exemplo, herdamos o controlador de help para que os títulos sejam coloridos.

https://gist.github.com/gullitmiranda/2fe8d9f1bc59a01cea63

Para visualizar o help global:

$ azk --help

Para visualizar o help apenas do comando agent:

$ azk agent --help

Gostaríamos de ressaltar que, apesar de ser empregada no azk, a biblioteca cli-router é um projeto open source independente e seu uso não depende do azk. Fique à vontade para utilizá-la para construir sua própria ferramenta de CLI. :)

Conclusão

A CLI do azk é a única central de comando da ferramenta para o usuário final (ainda…). Performance e facilidade de uso são requisitos centrais para o seu uso.

Demonstramos aqui como pensamos a evolução da CLI e as medidas que tomamos a fim de pô-la em prática.

Sabemos que, para se tornar o padrão universal de desenvolvimento que esperamos que o azk seja, ele nunca estará completo. Sempre haverá muito o que fazer para ele melhorar e atender o enésimo cenário de uso.

Como o azk é software de código aberto, buscamos com mais este exemplo de evolução de um componente do azk discutir "em público" o esforço envolvido na criação da ferramenta.

Podem continuar a contar com este tipo de esforço e de ritmo de evolução do azk vindos do nosso core team.

Como sempre, esperamos ansiosamente pelo dia em que, além disso, o azk possa contar também com ainda mais contribuições vindas de fora do nosso time.

Não deixe de conferir os links abaixo com as inúmeras formas de entrar em contato e de contribuir (e não apenas com código).

Pra começar, não esqueça de dar uma "estrela" para o azk no Github! ;)

https://github.com/azukiapp/azk

Valeu, pessoal!
Time Azuki

Mais sobre o azk

+ Site: http://azk.io
+ Github: https://github.com/azukiapp/azk
+ Documentação: http://docs.azk.io
+ Diretório de imagens criadas pelo time do azk: http://images.azk.io

Contribua com o azk

+ “Star” o azk no Github: https://github.com/azukiapp/azk
+ Reporte um problema: https://github.com/azukiapp/azk/issues/new
+ Ajude a resolver um problema reportado: https://github.com/azukiapp/azk/issues
+ Confira os sponsors do azk: http://azk.io/#sponsors

Fale com o time do azk

+ Assine a newsletter semanal: http://www.azk.io/#newsletter
+ Acompanhe o blog: https://medium.com/azuki-news
+ Fale com nosso suporte (chat): https://gitter.im/azukiapp/azk/pt (Português) e https://gitter.im/azukiapp/azk (English)
+ Facebook: https://www.facebook.com/azukiapp
+ Twitter: http://twitter.com/azukiapp
+ YouTube: https://www.youtube.com/user/Azukiapp/videos