Introdução ao desenvolvimento web com Gleam e frameworks emergentes na BEAM

1. Fundamentos do Gleam na BEAM

1.1. O que é Gleam: tipagem estática e sintaxe familiar para a máquina virtual Erlang (BEAM)

Gleam é uma linguagem de programação moderna que compila para a BEAM (Bogdan/Björn's Erlang Abstract Machine), a mesma máquina virtual que executa Erlang e Elixir. Sua principal característica é oferecer um sistema de tipos estáticos rigoroso, inspirado em linguagens como Haskell e Elm, combinado com uma sintaxe limpa e familiar para desenvolvedores que já trabalham com JavaScript, Python ou Rust.

// Exemplo básico de Gleam
pub fn saudacao(nome: String) -> String {
  "Olá, " <> nome <> "!"
}

pub fn main() {
  let mensagem = saudacao("Mundo")
  io.println(mensagem)
}

1.2. Vantagens da BEAM para web: concorrência leve, tolerância a falhas e escalabilidade

A BEAM foi projetada originalmente para sistemas telefônicos, onde falhas não são aceitáveis. Isso se traduz em três grandes vantagens para aplicações web:

  • Concorrência leve: processos BEAM são extremamente leves (alguns kilobytes cada), permitindo milhões de processos simultâneos sem sobrecarga significativa.
  • Tolerância a falhas: o modelo "let it crash" permite que processos falhem sem derrubar todo o sistema, com supervisores reiniciando automaticamente.
  • Escalabilidade: distribuição nativa entre nós, permitindo escalar horizontalmente com facilidade.

1.3. Configurando o ambiente: instalação do Gleam, compilador e ferramentas de build

Para começar, instale o compilador Gleam e as ferramentas necessárias:

# Instalação via script oficial (Linux/macOS)
curl -fsSL https://gleam.run/get | bash

# Verificar instalação
gleam --version

# Criar um novo projeto
gleam new meu_projeto_web
cd meu_projeto_web

# Compilar e executar
gleam build
gleam run

2. Primeiros passos com uma aplicação web em Gleam

2.1. Estrutura de um projeto web: dependências, módulos e ponto de entrada

Um projeto Gleam típico para web possui a seguinte estrutura:

meu_projeto_web/
├── gleam.toml          # Configuração do projeto e dependências
├── manifest.toml       # Lockfile de dependências
├── src/
│   ├── meu_projeto_web.gleam  # Módulo principal
│   └── ...
└── test/
    └── ...

2.2. Criando um servidor HTTP básico com a biblioteca mist

Mist é uma biblioteca HTTP minimalista para Gleam. Vamos criar um servidor simples:

// src/servidor.gleam
import gleam/io
import mist

pub fn main() {
  let servidor = mist.new()
  |> mist.port(8080)
  |> mist.handle_request(handle_request)
  |> mist.start

  io.println("Servidor rodando em http://localhost:8080")
}

fn handle_request(requisicao: mist.Request) -> mist.Response {
  mist.response(200)
  |> mist.set_body("Olá do Gleam!")
}

2.3. Roteamento simples: tratando requisições GET e POST com padrões de URL

Vamos adicionar roteamento básico:

import mist
import gleam/string

fn handle_request(requisicao: mist.Request) -> mist.Response {
  case requisicao.method, requisicao.path {
    "GET", "/" -> mist.response(200) |> mist.set_body("Página inicial")
    "GET", "/sobre" -> mist.response(200) |> mist.set_body("Sobre nós")
    "POST", "/api/dados" -> processar_dados(requisicao)
    _, _ -> mist.response(404) |> mist.set_body("Não encontrado")
  }
}

fn processar_dados(requisicao: mist.Request) -> mist.Response {
  // Lógica para processar dados POST
  mist.response(201) |> mist.set_body("Dados recebidos")
}

3. Frameworks emergentes na BEAM para desenvolvimento web

3.1. Wisp: framework minimalista e funcional para Gleam

Wisp é um framework web que oferece middleware, roteamento e suporte a sessões:

import wisp
import wisp/router

pub fn main() {
  let app = wisp.new()
  |> wisp.use(middleware_log)
  |> wisp.router(rotas)
  |> wisp.start(8080)
}

fn rotas(router: router.Router) -> router.Router {
  router
  |> router.get("/", fn(_) { wisp.html("Home") })
  |> router.get("/api/usuarios", listar_usuarios)
  |> router.post("/api/usuarios", criar_usuario)
}

3.2. Lustre: abordagem baseada em componentes reativos (model-view-update) para front-end

Lustre traz o padrão Elm (Model-View-Update) para o navegador, compilando Gleam para JavaScript:

// Componente contador
pub fn init() -> Int { 0 }

pub fn view(modelo: Int) -> String {
  "<div>
    <p>Contagem: " <> int.to_string(modelo) <> "</p>
    <button onclick='incrementar()'>+</button>
  </div>"
}

pub fn update(modelo: Int, mensagem: String) -> Int {
  case mensagem {
    "incrementar" -> modelo + 1
    _ -> modelo
  }
}

3.3. Outros projetos promissores: Gig (servidor HTTP assíncrono) e Gleam HTTP

  • Gig: servidor HTTP assíncrono focado em alta performance
  • Gleam HTTP: biblioteca cliente/servidor HTTP de baixo nível

4. Construindo uma API RESTful com Gleam e Wisp

4.1. Definição de endpoints e validação de parâmetros com tipos customizados

import wisp
import wisp/request
import gleam/dynamic

pub type Usuario {
  Usuario(id: Int, nome: String, email: String)
}

fn criar_usuario(requisicao: request.Request) -> wisp.Response {
  let dados = request.body_json(requisicao)

  case dados {
    Ok(json) -> {
      let nome = dynamic.field(json, "nome", dynamic.string)
      let email = dynamic.field(json, "email", dynamic.string)

      case nome, email {
        Ok(n), Ok(e) -> salvar_usuario(Usuario(id: 0, nome: n, email: e))
        _, _ -> wisp.json("{\"erro\": \"Campos inválidos\"}", 400)
      }
    }
    Error(_) -> wisp.json("{\"erro\": \"JSON inválido\"}", 400)
  }
}

4.2. Integração com banco de dados SQLite via gleam_sqlite

import gleam/sqlite

pub fn conectar_banco() -> Result(sqlite.Connection, String) {
  sqlite.open("dados.db")
}

pub fn listar_usuarios(conexao: sqlite.Connection) -> List(Usuario) {
  let consulta = sqlite.prepare(conexao, "SELECT id, nome, email FROM usuarios")

  sqlite.fetch_all(consulta)
  |> list.map(fn(linha) {
    Usuario(
      id: sqlite.column(linha, 0) |> int.parse |> result.unwrap(0),
      nome: sqlite.column(linha, 1),
      email: sqlite.column(linha, 2),
    )
  })
}

4.3. Tratamento de erros e respostas padronizadas (JSON, códigos HTTP)

pub type RespostaAPI(a) {
  Sucesso(dados: a)
  Erro(mensagem: String, codigo: Int)
}

pub fn resposta_json(resposta: RespostaAPI(a)) -> wisp.Response {
  case resposta {
    Sucesso(dados) -> wisp.json(dados, 200)
    Erro(msg, codigo) -> wisp.json("{\"erro\": \"" <> msg <> "\"}", codigo)
  }
}

5. Concorrência e estado compartilhado na web com BEAM

5.1. Modelo de atores no Gleam: criação de processos leves com gleam/otp

import gleam/otp/actor

pub type Mensagem {
  Incrementar
  ObterValor(remetente: actor.Address(Int))
}

pub fn contador_processo() -> actor.Behaviour(Mensagem, Int) {
  actor.behaviour(
    inicial: 0,
    handle: fn(estado, mensagem) {
      case mensagem {
        Incrementar -> actor.Continuar(estado + 1)
        ObterValor(remetente) -> {
          actor.send(remetente, estado)
          actor.Continuar(estado)
        }
      }
    }
  )
}

5.2. Gerenciamento de estado global com GenServers

import gleam/otp/gen_server

pub type Estado {
  Estado(visitas: Int)
}

pub fn iniciar_contador() -> Result(gen_server.Server(Estado), String) {
  gen_server.start_link(fn() { Estado(visitas: 0) }, [])
}

pub fn registrar_visita(servidor: gen_server.Server(Estado)) {
  gen_server.call(servidor, fn(estado) {
    #(Estado(visitas: estado.visitas + 1), estado.visitas + 1)
  })
}

5.3. Supervisão e resiliência: árvores de supervisão para serviços web tolerantes a falhas

import gleam/otp/supervisor

pub fn iniciar_sistema() {
  let criancas = [
    supervisor.child(contador_processo, id: "contador_visitas"),
    supervisor.child(servidor_http, id: "servidor_web"),
  ]

  supervisor.start_link(
    supervisor.strategy_one_for_one(),
    criancas
  )
}

6. Testes e boas práticas no ecossistema Gleam

6.1. Testes unitários e de integração com gleeunit

// test/test_usuarios.gleam
import gleeunit
import gleeunit/should

pub fn test_criar_usuario_valido() {
  let resultado = criar_usuario("João", "joao@email.com")
  resultado |> should.be_ok
}

pub fn test_email_invalido() {
  let resultado = criar_usuario("Maria", "email-invalido")
  resultado |> should.be_error
}

pub fn main() {
  gleeunit.main()
}

6.2. Mocking de requisições HTTP e simulação de falhas

import gleam/http

pub fn test_resposta_api() {
  let mock_resposta = http.Response(
    status: 200,
    headers: [],
    body: "{\"mensagem\": \"OK\"}"
  )

  processar_resposta(mock_resposta) |> should.equal("OK")
}

6.3. Padrões de projeto: separação de lógica de negócio, middlewares e configuração

Organize seu projeto em camadas:

src/
├── api/          # Rotas e controladores
│   ├── usuarios.gleam
│   └── produtos.gleam
├── dominio/      # Lógica de negócio
│   ├── entidades.gleam
│   └── servicos.gleam
├── infra/        # Banco de dados, cache
│   ├── repositorio.gleam
│   └── cache.gleam
└── config/       # Configurações
    └── configuracao.gleam

7. Deploy e desempenho de aplicações Gleam

7.1. Compilação para Erlang e execução em clusters BEAM

# Compilar para Erlang
gleam build --target erlang

# Executar em modo distribuído
erl -sname no1@localhost -setcookie segredo -run meu_app start
erl -sname no2@localhost -setcookie segredo -run meu_app start

7.2. Otimizações: uso de ETS para cache, pool de conexões

import gleam/ets

pub fn criar_cache() -> ets.Table {
  ets.new("cache_web", [ets.public, ets.named_table])
}

pub fn armazenar_cache(tabela: ets.Table, chave: String, valor: String) {
  ets.insert(tabela, #(chave, valor))
}

7.3. Exemplos práticos: deploy em servidores leves com Docker

FROM ghcr.io/gleam-lang/gleam:latest AS builder
WORKDIR /app
COPY . .
RUN gleam build --target erlang

FROM erlang:26-alpine
WORKDIR /app
COPY --from=builder /app/build/prod/rel .
EXPOSE 8080
CMD ["bin/meu_app", "start"]

Referências