Thiago Holanda (@tholanda), desenvolvedor de software há um bocado de tempo, já trabalhou em tudo que foi tipo de projeto. Começou no backend, se meteu a besta com banco de dados, iniciou no iOS, se embrenhou pelo client side, voltou pro iOS e agora voltou a namorar o backend, ufa… Marido da Carolina, pai de duas trombadinhas felinas e quando tem um tempinho dá uns rolés de skate por aí, que é a terceira maior paixão, depois da Carolina e da programação!
Um pequeno overview
Recentemente, enquanto preparava uma apresentação sobre “Swift no Backend” para o encontro de desenvolvedores de uma grande empresa, conversei com alguns amigos no Slack, nos corredores da empresa e percebi que nos dias de hoje temos excelentes desenvolvedores móveis, que por muitas vezes não conhecem outras tecnologias, senão iOS ou Android.
Pessoas que acompanharam a popularização dessas novas tecnologias através da Apple e do Google, investiram tempo e dinheiro nesse segmento, e deixaram de lado o restante das engrenagens que fazem esse grande relógio chamado World Wide Web funcionar.
Eu mesmo, durante alguns anos, deixei o backend de lado e esquecido acreditando que parte de um novo futuro estava nas mãos de empresas que estavam surgindo sob uma nova sigla: BaaS (Backend as a Service) . Achei que o Parse era a grande solução, quase uma bala de prata. Desenvolvi alguns sistemas usando a ferramenta e não posso dizer que me arrependi.
Depois de um tempo trilhando esse caminho, comecei a perceber quão intrusivo era o Parse. Queria me livrar das garras dele e então renasceu a idéia de me dedicar mais ao backend e buscar alternativas de código aberto, que fosse semelhante ao que o Parse poderia me oferecer. Nessa busca, tinha em mente algumas coisas: 1) open source, 2) Swift, 3) backend, e é claro, iniciar o próximo projeto em Swift.
Por falta de tempo ou vontade de sair da zona de conforto, esse projeto em Swift não foi iniciado. Finalmente, no início de Dezembro de 2015, a Apple libera o código fonte do Swift, o que foi ótimo. Em Janeiro, conversando com o Ricardo Borelli, percebemos que havia uma vontade mútua de aprender o Swift, mas não para trabalhar no iOS. Queríamos aprender a linguagem de programação, não o Framework. A pergunta nesse momento, era: “Como?”.
As formas que tínhamos para iniciar nosso aprendizado no Swift eram:
- Desenvolvendo uma aplicação iOS (fora do nosso objetivo inicial)
- Contribuir com projeto open source escrito em Swift
Por termos trabalhado alguns anos como desenvolvedores Backend, a segunda opção era mais atraente, então iniciamos as buscas por projetos open source que nos permitissem desenvolver web applications usando Swift.
Primeiro chegamos ao CocoaPods App. Desencantamos, pois era um projeto com diversas classes escritas em Objective-C. Em seguida, encontramos o Perfect, o Vapor, e então o Ricardo encontrou o Zewo. A partir daí começou de fato o envolvimento com um projeto 100% open source para servidor, onde aprenderíamos Swift.
O que é o Zewo?
O Zewo é um conjunto de bibliotecas voltadas para o desenvolvimento de software para servidores. Diversas bibliotecas do Zewo são nada mais do que wrappers de classes, que foram escritas em C.
A História do Zewo
O Zewo nasceu em torno de Junho de 2015, na época em que a Apple anunciou que abriria o código fonte do Swift. O primeiro nome dele foi SwiftHTTPServer e foi construído pelo Paulo Faria - um dev brasileiro - que manteve a construção do SwiftHTTPServer em constante crescimento, até pouco antes da Apple finalmente liberar o código fonte do Swift, quando ele decidiu reiniciar tudo. E então surgiu o Zewo com uma proposta diferente: “não ser apenas um web framework, mas uma plataforma de desenvolvimento para tudo que fosse relacionado a servidor”. E tudo isso graças ao fato de compilar para Linux.
Se é uma plataforma para servidores, me dá uns exemplos do que eu posso fazer com o Zewo.
Você pode trabalhar com praticamente tudo que você quiser no servidor. Alguns poucos exemplos:
- Crawlers
- Loggers
- Renders
- Database (MySQL e Postgres)
- Scripts
- Web Applications
- Conexões remotas usando TCP/UDP
E o mais interessante: sem precisar da Foundation, que ainda não possui o código fonte liberado.
Zewo + Swift X
Desde o início, modularização foi um dos focos principais do Zewo. Isso faz com que a reutilização de código ocorra de forma muito mais fácil. Quando Vapor (um dos frameworks web para swift) resolveu se integrar aos módulos do Zewo, surgiu a idéia de criar um padrão para compartilhamento de código entre projetos Swift. Nasceu então o Swift X (Swift Cross Project Standards). O Swift X é uma coleção de protocolos e tipos básicos, passando por dados binários, requisições e respostas HTTP, URIs, até alcançar um padrão para servidores e clientes HTTP, roteadores HTTP, middlewares, etc.
O Swift X ainda é bem novo, mas já tem alguns frameworks iniciando o suporte a seus padrões. A próxima versão do Zewo já vai fazer uso do SX, facilitando ainda mais o compartilhamento de código e integração entre a comunidade Swift.
Vamos começar?
Você precisa de algumas ferramentas. Vâmo lá, receita de bolo 😜:
- Homebrew
- Um editor de texto
- CodeRunner
- Sublime Text
- Atom
- TextMate
- Você não manda em mim, vou usar o TextEdit 😂
Depois de instalado, será necessário adicionar a fórmula do Zewo para o Homebrew. No terminal, digite o seguinte comando
$ brew tap zewo/tap
$ brew install zewo
Toolchain
Para iniciar os trabalhos de forma simples, usando o Mac OS X, precisaremos do toolchain do Swift.
Se você já tem tudo isso instalado, vá direto para Criando o Hello World
Faça o download do toolchain de 08 de Fevereiro em https://swift.org/download/#apple-platforms
Finalizado o download, execute o swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-osx.pkg
Será necessário que você adicione à sua variável de ambiente, o path do toolchain que foi instalado.
Abra o terminal e digite o seguinte comando
$ open ~/.bash_profile
Adicione o path padrão de instalação do Swift (caso você não tenha alterado o diretório de instalação)
/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin
O resultado final da variável de ambiente PATH deverá ser algo semelhante ao screenshot. Salve a modificação no .bash_profile.
Feito isso, será necessário executar o .bash_profile, usando o comando abaixo
$ source ~/.bash_profile
Criando um Hello World
Vamos voltar ao terminal
$ cd ~/
$ mkdir hello-world
$ cd hello-world
$ swift-build --init
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift
Creating Tests/
Com um editor de texto, edite o arquivo Package.swift e adicione os repositórios HTTPServer, Router e LogMiddleware à nossa lista de dependências
Package.swift
import PackageDescription
let package = Package(
name: "hello-world",
dependencies: [
.Package(url: "https://github.com/Zewo/HTTPServer.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/Router.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/LogMiddleware.git", majorVersion: 0, minor: 3)
]
)
Sources/main.swift
import HTTPServer
import Router
import LogMiddleware
let log = Log()
let logMiddleware = LogMiddleware(log: log)
let router = Router(middleware: logMiddleware) { route in
route.get("/") { request in
return Response(body: "Hello World!")
}
}
try Server(port: 8080, responder: router).start()
Após implementar essas milhares poucas linhas de código volte ao terminal, no diretório do projeto, e execute o compilador do Swift
$ swift-build
Na primeira vez que você executar o swift-build
no diretório do seu projeto, o Swift Package Manager fará o clone dos projetos que adicionamos na chave dependencies
, no Package.swift
e suas subdependências, em seguida os sources serão compilados. Por fim, o resultado será um executável gerado pelo compilador
$ swift-build
Compiling Swift Module 'helloworld' (1 sources)
Linking Executable: .build/debug/hello-world
Agora que o compilador disse pra gente o que compilou e onde gerou o executável, fica fácil saber o que executar.
Assumindo que você está no diretório hello-world
, digite o seguinte no terminal
$ .build/debug/hello-world
_____
,.-``-._.-``-., /__ / ___ _ ______
|`-._,.-`-.,_.-`| / / / _ \ | /| / / __ \
| |ˆ-. .-`| | / /__/ __/ |/ |/ / /_/ /
`-.,| | |,.-` /____/\___/|__/|__/\____/ (c)
`-.,|,.-` -----------------------------
================================================================================
Started HTTP server, listening on port 8080.
Agora que temos o nosso servidor rodando devidamente no terminal, abra o browser e digite a seguinte URL: http://127.0.0.1:8080
e você verá algo como o screeshot abaixo
Por ter configurado o LogMiddleware no Package.swift
, as requisições disparadas contra o servidor serão mostradas no console.
Em diversos web frameworks, mostrar os detalhes da requisição no console é parte da configuração padrão. Como no Zewo separamos todos os módulos, deixamos essa configuração como opção para o usuário. No Hello World não estamos persistindo esses dados, mas caso seja interessante, você pode usar o File para guardá-los em um arquivo.
Enfim, depois de tanta explicação, esse é o nosso Hello World usando Swift e Zewo.
Por que não incrementar um pouco mais esse exemplo ? Fique tranquilo que a partir de agora não serei tão detalhista! será? 😌
Criando um render html
Legal, bacana, temos o Hello World funcionando. Mas se estamos falando sobre montar uma web application, então é necessário renderizar um HTML com informações vindas do sevidor, não é mesmo ? Por isso, vamos usar o Sideburns - uma pequena camada sobre o Mustache que é um fork do GRMustache - e o HTTPFile que é a implementação do protocolo ResponderType e será usado para expor o conteúdo da pasta public
.
Ao adicionar o Sideburns à lista de dependencies
, oPackage.swift
deverá ficar assim
import PackageDescription
let package = Package(
name: "hello-world",
dependencies: [
.Package(url: "https://github.com/Zewo/HTTPServer.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/Router.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/LogMiddleware.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/HTTPFile.git", majorVersion: 0, minor: 3),
.Package(url: "https://github.com/Zewo/Sideburns.git", majorVersion: 0, minor: 3)
]
)
Show! Logo após adicionar os novos pacotes, o ideal seria voltar ao terminal e rodar o swift-build
, mas podemos esperar um pouquinho mais e fazer tudo de uma vez.
Mas se paciência não for muito seu forte, manda ver! Mal não vai fazer! 😜
Na raiz do seu projeto vamos criar uma pasta chamada public
e vamos adicionar alguns arquivos simples. No terminal digite
$ mkdir -p public/assets
crie um arquivo main.css
dentro da pasta assets
$ touch public/assets/main.css
edite o main.css
e vamos adicionar uma simples propriedade, apenas para que a nossa página não fique completamente sem estilo
body {
font-family: "verdana";
font-size: 10px;
}
crie na raiz da pasta public
um arquivo chamado hello.html
$ touch public/hello.html
edite o hello.html
e adicione o seguinte bloco ao arquivo
caso você tenha dúvidas sobre a forma de trabalhar com o Mustache, acesse a documentação da ferramenta no link https://github.com/groue/GRMustache.
<html>
<head>
<title>{{ title }}</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="/assets/main.css">
</head>
<body>
<div>
<h1>
{{ description }}
</h1>
<ul>
{{# messages }}
<li> {{ author }} - {{ message }} </li>
{{/ messages }}
</ul>
</div>
</body>
</html>
O que adicionaremos agora ao nosso Sources/main.swift
:
- Estrutura de dados para ser renderizado no
html
, que será o response do endpoint “/” - Response com path do arquivo que será usado para renderizar o dicionário que acabamos de adicionar
FileResponder
, que será responsável por expor os arquivos da pastapublic
de forma estática
O resultado final do nosso main.swift
import HTTPServer
import Router
import LogMiddleware
import HTTPFile
import Mustache
import Sideburns
let log = Log()
let logMiddleware = LogMiddleware(log: log)
let router = Router(middleware: logMiddleware) { route in
route.get("/") { request in
let data: [String: Any] = [
"title": "Título da Página",
"description": "Renderizando HTML usando Sideburns",
"messages": [
[
"author": "Patrick",
"message": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
],
[
"author": "Senhor Sirigueijo",
"message": "Morbi luctus urna vel lacus malesuada posuere."
],
[
"author": "Lula Molusco",
"message": "Praesent dignissim nunc a convallis posuere."
],
[
"author": "Bob Esponja",
"message": "Nam facilisis arcu at consequat sagittis."
]
]
]
return try Response(templatePath: "public/hello.html", templateData: data)
}
route.fallback = FileResponder(basePath: "public")
}
try Server(port: 8080, responder: router).start()
Ok, terminamos de incrementar o nosso Hello World com a renderização de algumas informações do servidor. Nesse caso, os dados estão todos “mockados”, mas poderiam vir diretamente de um banco (que é assunto para um próximo artigo 😉). Agora vamos compilar as nossas mudanças e executar o binário gerado pelo compilador. Para isso, volte ao terminal e execute
$ swift-build
$ .build/debug/hello-world
Abra o browser e digite o endereço http://127.0.0.1:8080
. Ao carregar a tela, esse deve ser o resultado
e o html
será parecido com que está listado abaixo
<html>
<head>
<title>Título da Página</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="/assets/main.css">
</head>
<body>
<div>
<h1>
Renderizando HTML usando Sideburns
</h1>
<ul>
<li> Patrick - Lorem ipsum dolor sit amet, consectetur adipiscing elit. </li>
<li> Senhor Sirigueijo - Morbi luctus urna vel lacus malesuada posuere. </li>
<li> Lula Molusco - Praesent dignissim nunc a convallis posuere. </li>
<li> Bob Esponja - Nam facilisis arcu at consequat sagittis. </li>
</ul>
</div>
</body>
</html>
e por aqui, finalizamos mais um exemplo de como podemos renderizar valores em um html
.
Criando uma API simples
Agora, vamos usar um repositório que já tem como dependência diversos outros que precisamos para web applications - o Zewo - um repositório que podemos chamar de umbrella package, fazendo uma referência a umbrella header.
Por conta de diversas libraries ainda estarem sendo escritas, principalmente no caso de rotas RESTful, nesse artigo veremos apenas as rotas de consulta mais simples. Em breve escreverei outro post abordando APIs mais complexas, prometo 😉🙏!
Nesse exemplo criaremos uma API ridiculamente simples. O objetivo não é ser complexo e por isso não faria sentido criar muitos métodos.
Então vamos começar criando um novo pacote para a nossa API ? De volta ao terminal crie uma nova pasta com o nome todo-api
$ cd ~/
$ mkdir todo-api
$ cd todo-api
$ swift-build --init
Creating Package.swift
Creating .gitignore
Creating Sources/
Creating Sources/main.swift
Creating Tests/
vamos editar o arquivo Package.swift
e adicionar a única dependência que precisaremos, o Zewo
Se você quiser conhecer quais são as dependências usadas por esse repositório, acesse essa url: https://github.com/Zewo/Zewo/blob/master/Package.swift
import PackageDescription
let package = Package(
name: "todo-api",
dependencies: [
.Package(url: "https://github.com/Zewo/Zewo", majorVersion: 0, minor: 3)
]
)
agora nós precisamos criar de fato as nossas rotas e respostas, sendo assim, edite o arquivo Sources/main.swift
e cole o conteúdo do bloco abaixo
import HTTPServer
import Router
import LogMiddleware
import HTTPFile
import ContentNegotiationMiddleware
import LogMiddleware
import JSONMediaType
import InterchangeData
let log = Log()
let logMiddleware = LogMiddleware(log: log)
let contentNegotiaton = ContentNegotiationMiddleware(mediaTypes: JSONMediaType())
let router = Router(middleware: logMiddleware, contentNegotiaton) { route in
var items: [InterchangeData] = [
["id": "1", "description": "Comprar frutas"],
["id": "5", "description": "Pagar condomínio"],
["id": "3", "description": "Despachar encomenda"],
["id": "6", "description": "Assistir Deadpool"],
["id": "4", "description": "Ir ao banco"],
["id": "2", "description": "Ligar para operadora"]
]
func get(id: String) throws -> InterchangeData {
let list = items.filter({ $0["id"]!.string == id })
guard let todo = list.first else {
return nil
}
return todo
}
// cria o novo registro de uma tarefa
route.post("/todo") { request in
let body = request.content!
let id = InterchangeData.from(String(arc4random()))
let description = body["description"]
let todo: InterchangeData = [
"id" : id,
"description" : description!
]
items.append(todo)
return Response(status: .Created, content: todo)
}
// Lista todas os registros de tarefas
route.get("/todo") { request in
items = items.sort({ $0["id"]!.string < $1["id"]!.string })
guard items.count > 0 else {
return Response(status: .NoContent)
}
return Response(content: InterchangeData.from(items))
}
// busca os dados de uma tarefa baseado no id
route.get("/todo/:id") { request in
let path = request.pathParameters
let todo = try get(path["id"]!)
guard todo != nil else {
return Response(status: .NotFound)
}
return Response(content: todo)
}
// atualiza uma tarefa baseado no id
route.put("/todo/:id") { request in
let path = request.pathParameters
let body = request.content!
var todo = try get(path["id"]!)
guard todo != nil else {
return Response(status: .NotFound)
}
let index = items.indexOf(todo)!
todo["description"] = body["description"]
items[index] = todo
return Response(status: .NoContent)
}
// exclui uma tarefa baseado no id
route.delete("/todo/:id") { request in
let path = request.pathParameters
let todo = try get(path["id"]!)
guard todo != nil else {
return Response(status: .NotFound)
}
let index = items.indexOf(todo)!
guard items.removeAtIndex(index) != nil else {
return Response(status: .NotFound)
}
return Response(status: .NoContent)
}
}
try Server(port: 8080, responder: router).start()
É claro que você, um expert em Swift, pode criticar diversos pontos do bloco acima, infelizmente ainda não faço parte desse seleto time, mas estamos à caminho.
Neste exemplo, criamos:
- Criação de uma tarefa
- Listagem das tarefas
- Detalhe de uma tarefa
- Atualização de uma tarefa
- Exclusão de uma tarefa
Em princípio, para que não ficasse com mais dependências (ex: banco de dados), usamos um array mutável para persistir e listar as tarefas. É claro que essa não chega nem a ser uma forma decente de persistência, mas atende ao nosso propósito educativo.
ContentNegotiationMiddleware
Esse middleware tem como função básica o tratamento do tipo de informação recebida no request. É baseado no
Content-Type
e noAccept
. No nosso exemplo, trabalharemos apenas comJSON
. Caso haja necessidade, você pode optar por usar o URLEncodedForm.
Depois de rodar o exemplo anterior, você pode estar perguntando: como proteger a nossa humilde API ? Bom, temos à disposição diversas ferramentas para nos auxiliar nesse ponto, mas por enquanto, apenas um está implementado de forma nativa no Zewo: o BasicAuthMiddleware (RFC 2617).
Infelizmente, o HTTP Basic Authentication não é seguro em relação ao dado trafegado. Encriptar os dados requer Base64, uma encriptação de duas vias, ou seja, a segurança é tão fraca que, qualquer pessoa que interceptar a sua conexão com o servidor, facilmente descobrirá qual usuário e senha está sendo transmitido. Se você usa HTTPS, a vida de quem está tentando interceptar os dados trafegados complica um pouco mais.
Como adicionar o BasicAuth nas minhas rotas ?
Em outras ocasiões, você precisaria adicionar o pacote BasicAuthMiddleware na lista de dependências do Package.swift
, mas nós estamos usando o Zewo, que é o nosso umbrella package e ele já possui essa dependência. Então, ao invés de trabalhar com diversas dependências, nós vamos voltar a editar o Sources/main.swift
.
Importe o BasicAuthMiddleware
no topo do main.swift
import BasicAuthMiddleware
vamos instanciar o BasicAuthMiddleware
let basicAuth = BasicAuthMiddleware { username, password in
if username == "admin" && password == "password" {
return .Authenticated
}
return .AccessDenied
}
e configurar no nosso Router
. Basta adicionar a variável basicAuth
ao parâmetro middleware
e deve ficar assim
let router = Router(middleware: logMiddleware, contentNegotiaton, basicAuth) { route in
O que precisávamos para “segurar” a nossa API era basicamente esses dois passos. O resultado disso é simples: se você não informar as credenciais no momento da requisição, a API recusará o seu acesso.
Ao informar credenciais válidas, a API devolverá os dados normalmente é efetuará as devidas ações, como: insert, update, delete e quantos mais você configurar
e finalmente, terminamos por aqui o terceiro e último exemplo do nosso artigo.
Considerações finais
Como você pode ver, o desenvolvimento de web applications não é realmente um monstro de sete cabeças como talvez você estivesse imaginando. É claro que no desenvolvimento de software tudo pode piorar, e pode apostar que vai. Com um pouco de estudo, dedicação, fonte de pesquisa (tá bom, Stack Overflow vale também 😛) e uma boa documentação, você pode e DEVE ir longe.
Durante o tempo em que estive escrevendo esse post, a Apple liberou mais um toolchain com diversas novas possibilidades que, com toda certeza, nos ajudarão muito daqui pra frente. Pelo fato de agora o Swift também compilar C, podemos fazer deploy das aplicações escritas em Swift para o Heroku, mas isso também ficará para uma próxima vez.
Os códigos dos exemplos estão todos no meu perfil do Github, na url https://github.com/unnamedd/equinocios-backend
Hoje, dia 20 de Março, seria o nosso último dia de EquinociOS, mas devido a tantos ótimos desenvolvedores se interessarem pelo projeto, foi decidido que prorrogaríamos até o final do mês. Isso quer dizer que até o dia 31 de Março, você vai poder acompanhar artigos inéditos sobre o mundo de desenvolvimento da Apple.
Esse post teve a participação direta de diversos amigos, sugerindo, corrigindo ou ensinando.
Meus agradecimentos ao Paulo Faria, ao Ricardo Borelli e a minha amada Carolina.
A foto de capa é da Via Láctea numa belíssima noite!