Introdução ao TypeSpec
Vou começar esse post com um pouco de história. Lá pelo começo da década de 2010, o hype do momento era o conceito de APIs e API-first. É algo que parece trivial hoje em dia, mas precisamos lembrar que a tecnologia vigente antes disso era o SOAP e seus arquivos XML gigantes. Então, APIs leves usando JSON e respeitando os conceitos REST, que tinham sido inventados alguns anos antes, eram uma evolução enorme.
Neste contexto, começou uma “disputa” entre algumas linguagens de especificação de APIs, pois com mais e mais times adotando o conceito era necessário algo que fosse possível definir, documentar e detalhá-las. Dentre os competidores consigo lembrar do RAML e o que eu estava apostando minhas fichas, o API Blueprint, mas o vencedor foi o Swagger, mais tarde renomeado para OpenAPI.
Acelera para 2024 e todo mundo está feliz escrevendo e mantendo arquivos YAML de centenas, as vezes milhares de linhas. Certo?
Pelo visto não sou só eu que tenho essa impressão, pois no começo de 2024 a Microsoft lançou um novo projeto Open Source chamado TypeSpec. Trata-se de um formato baseado fortemente no TypeScript, também criado pela Microsoft, que permite a definição de APIs de maneira simplificada.
Quem acompanha mais de perto a história do padrão OpenAPI deve achar esse movimento bem estranho, pois a Microsoft é uma das entidades membro do padrão. Em uma talk uma das líderes do projeto TypeSpec comentou os motivos da criação do novo padrão:
- O OpenAPI foi percebido pelas equipes da Microsoft como difícil de escrever, revisar e manter.
- Era difícil seguir as diretrizes da API nesse contexto, especialmente ao trabalhar com muitas equipes novas que “conheciam” o OpenAPI.
- O OpenAPI não domina o mundo da Microsoft em termos de linguagens e protocolos de descrição de API, com outros, como o gRPC, sendo suportados.
Neste momento algum dos leitores pode estar se perguntando algo como:
Não vejo problemas na OpenAPI, afinal eu apenas gero a documentação a partir do meu código.
Realmente, essa é uma abordagem adotada por alguns times, usando ferramentas como o swaggo. Vejo alguns problemas nessa abordagem, como código “poluído” com comentários e anotações e o fato de que a API acaba sendo muito influenciada pelo estilo de programação do time.
Mas existe outra abordagem, que na minha opinião é a melhor: a design-first. Nela os times primeiro definem o design da API, pensando no seu consumidor, nos recursos que vão ser expostos, na evolução da mesma. Com isso em mente é preciso ter uma forma simples de documentar este design, que depois será implementado e usado pelos clientes. E eu não usaria a palavra “simples” para definir um documento escrito no padrão OpenAPI…
Vamos colocar a mão na massa para testar isso.
O primeiro passo é a instalação do CLI do TypeSpec, usando o comando:
npm install -g @typespec/compiler
No próximo passo eu criei um diretório e inicializei um projeto:
mkdir post-typespec
cd post-typespec/
tsp init
No menu de opções eu selecionei Generic REST API
e aceitei as opções padrão.
A seguinte estrutura foi criada:
❯ ls -lha
total 32
drwxr-xr-x 6 eminetto staff 192B 13 Out 09:51 .
drwxr-xr-x 80 eminetto staff 2,5K 13 Out 09:50 ..
-rw-r--r-- 1 eminetto staff 102B 13 Out 09:51 .gitignore
-rw-r--r-- 1 eminetto staff 79B 13 Out 09:51 main.tsp
-rw-r--r-- 1 eminetto staff 417B 13 Out 09:51 package.json
-rw-r--r-- 1 eminetto staff 31B 13 Out 09:51 tspconfig.yaml
No arquivo package.json
constam as dependências do projeto, que podem ser instaladas com o comando:
tsp install
Com as dependências instaladas podemos descrever a nossa API no arquivo main.tsp
. Neste caso usei o exemplo da documentação oficial:
import "@typespec/http";
using TypeSpec.Http;
model Pet {
name: string;
age: int32;
}
model Store {
name: string;
address: Address;
}
model Address {
street: string;
city: string;
}
@route("/pets")
interface Pets {
list(@query filter: string): Pet[];
create(@body pet: Pet): Pet;
read(@path id: string): Pet;
}
@route("/stores")
interface Stores {
list(@query filter: string): Store[];
read(@path id: string): Store;
}
Com essa definição podemos gerar as especificações usando o conceito de Emitters
. Vou voltar a este assunto em breve, mas por enquanto vamos usá-lo para gerar a especificação OpenAPI. Para isso, no arquivo tspconfig.yaml
temos o seguinte código gerado:
emit:
- "@typespec/openapi3"
Ao usar o comando a seguir temos o documento yaml
gerado dentro do diretório tsp-output/@typespec/openapi3/openapi.yaml
:
tsp compile .
Também é possível usar o comando a seguir para que a compilação seja feita automaticamente conforme os arquivos dentro do projeto forem alterados:
tsp compile . --watch
Fazendo uma comparação de tamanho de arquivos, as 31 linhas de TypeSpec tornam-se 125 linhas de OpenAPI no formato YAML! Os dois arquivos podem ser comparados na documentação oficial.
Voltando as Emitters
. Além do OpenAPI que vimos em funcionamento, existem opções para geração das especificações em JSON Schema e Protobuf. Mas mais importante, é possível a criação de novos emitters para geração de códigos, SDKs, outros formatos de documentação, etc. Por exemplo, poderíamos ter um Emitter que lê a especificação em TypeSpec e gera configurações para algum API Gateway como o Kong ou o Traefik (dei esse exemplo pois é algo que eu quero testar).
Um argumento contra a adoção do TypeSpec pode ser a inclusão de um novo componente na stack do time. Outro pode ser a maturidade baixa do projeto, pois tem menos de um ano (como projeto Open Source), apesar da evolução rápida que tem apresentado, bem como o uso dentro da Microsoft.
São argumentos válidos e que devem ser considerados sempre que uma nova tecnologia é cogitada para um projeto. Meu objetivo com esse post foi apresentar esta nova opção e deixar a sugestão de observarmos ela de perto, pois pode ser de grande valia em projetos grandes. Com certeza eu vou manter ela na minha lista de ferramentas a testar e validar.
Qual sua opinião sobre o assunto? Adoraria ouvir opiniões, principalmente do #teamOpenAPI
:)