Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Escolhendo (ou não) o melhor ORM para o seu projeto

Escolhendo (ou não) o melhor ORM para o seu projeto

Andreia Silva

May 10, 2024
Tweet

More Decks by Andreia Silva

Other Decks in Programming

Transcript

  1. Escolhendo (ou não) o melhor ORM para o seu projeto

    <Andreia Silva> <Senior Software Engineer @Toggl> <10/05/2024>
  2. ORMs (Object Relational Mapping Database) ORM é uma técnica que

    representa uma ponte entre os objetos do nosso domínio e tabelas em bases de dados relacionais, como o Postgres e MySQL. <Definição>
  3. • Produtividade ◦ Principalmente no desenvolvimento de CRUDs ◦ Suporte

    a diversas funcionalidades • Developer-friendly ◦ Foco na linguagem • Mecanismos que ajudam a mitigar problemas de segurança • Suporte a diferentes bases de dados <Todo mundo ama ORM>
  4. • ORMs condicionam o uso de SELECT * FROM através

    de algumas funções. Por exemplo: o.db.First(book, bookID) // Gorm o.db.NewSelect().Model(book). Where("id=?",bookID).Scan(o.ctx) // Bun <Todo mundo odeia ORM>
  5. Quando pensamos em 5 campos, isso não parece muito. 📚Vale

    a pena ler: What ORMs have taught me: just learn SQL. <Todo mundo odeia ORM>
  6. • Menos controle • Curva de aprendizado da ferramenta •

    SQL não é uma sub-linguagem • Falta de flexibilidade para atender queries complexas • A ferramenta de ORM também pode ser burlada <Todo mundo odeia ORM>
  7. Alguns fatores podem influenciar na decisão de adotar ou não

    uma ferramenta de ORM em um projeto. • Escopo • Criticidade • Prazo de entrega • Volume de dados a serem processados <Então, o melhor ORM é não ter ORM?>
  8. • A maior parte das operações eram CRUDs • Pequena

    massa de dados • Apesar de queries serem simples, ainda precisavam ser escritas • Incertezas em relação a continuidade do produto <Historinha de quando usei o Gorm>
  9. Voltando as mazelas dos ORMs, notou que ainda não falamos

    sobre Desempenho? <Calma calabresos!>
  10. • Escrita e execução de queries nativas ◦ database/sql ◦

    pgx ou lib/pq (Postgres)? • SQL builders e SQL-First ORMs, ex. squirrel, *bun • Code-first ORMs, ex. gorm, ent • Geradores de código Go a partir de queries SQL, ex. sqlc <Alternativas>
  11. • ⭐35.1k • Code-first • Suporta MySQL, PostgreSQL, SQLite, SQL

    Server, e TiDB • Comunidade ativa e documentação abrangente • É uma biblioteca rica em features ◦ Bulk insert, auto migrations, hooks, transações & transações aninhadas, gerenciamento automático de campos como created_at, updated_at e deleted_at <Gorm>
  12. • Updates globais desabilitados por padrão. o.db.Model(&book).Update("title", "New Title") ◦

    ErrMissingWhereClause • E se a gente tentar com Raw Query? o.db.Raw("UPDATE books SET title = ?", "Era uma vez um dev...") <Gorm>
  13. • Existe uma seção na documentação que traz dicas de

    configurações para melhoria de performance ◦ Desabilitar transação padrão ◦ Prepared Statement cache ◦ Selecionar campos específicos db.Select("Name", "Age").Find(&Users{}) <Gorm e performance>
  14. • ⭐3.1k • É uma reescrita do go-pg • SQL-first

    ORM ◦ APIs que remetem a um SQL builder que lembram instruções SQL • Suporta PostgreSQL, MySQL, MSSQL e SQLite • Documentação clara e objetiva • Suporte a migrações versionadas, transações, bulk insert-delete-update, soft delete, debug, observabilidade • Sugere Cursor Pagination para Postgres e MySQL <Bun>
  15. Você utiliza offset para fazer paginação? 📚Vale a pena ler:

    No offset - Use the index, Luke! <Cursor Pagination>
  16. regionalSales := db.NewSelect(). ColumnExpr("region"). ColumnExpr("SUM(amount) AS total_sales"). TableExpr("orders"). GroupExpr("region") topRegions

    := db.NewSelect(). ColumnExpr("region"). TableExpr("regional_sales"). Where("total_sales > (SELECT SUM(total_sales) / 10 FROM regional_sales)") var []items map[string]interface{} err := db.NewSelect(). With("regional_sales", regionalSales). With("top_regions", topRegions). ColumnExpr("region"). ColumnExpr("product"). ColumnExpr("SUM(quantity) AS product_units"). ColumnExpr("SUM(amount) AS product_sales"). TableExpr("orders"). Where("region IN (SELECT region FROM top_regions)"). GroupExpr("region"). GroupExpr("product"). Scan(ctx, &items) • O Bun também fornece as mesmas travas de updates globais e placeholders para evitar SQL injection. ⇠ Exemplo de query avançada <Bun>
  17. • ⭐14.9k • Suporta SQLite, PostgreSQL, MySQL/MariaDB, Gremlin, entre outros

    • Code-first • Criado em 2019 por desenvolvedores do Meta ◦ Facebook > Linux Foundation • Código gerado a partir de schemas ◦ Fields = propriedades ou colunas ◦ Edges = relacionamentos <Ent>
  18. • Tipagem estática baseada na geração de código ◦ db.Where("title

    = ?", "New Title").Find(&book) // Gorm ◦ o.db.NewSelect().Model(&book). Where("title = ?", "New Title").Scan(o.ctx) // Bun ◦ db.Book.Query(). Where(book.TitleEQ("New Title")).First(ctx) // Ent <Ent>
  19. • ⭐10.9k • Gerador de código • Oficialmente suporta PostgreSQL,

    MySQL/MariaDB, e SQLite • Nada de queries obscuras geradas em runtime • Suporta query lints customizáveis 📚Vale a pena ler: Criando uma API usando Go e sqlc - Elton Minetto <Sqlc>
  20. • Geração de código a partir de queries em tempo

    de compilação ◦ Anotações <Sqlc> -- name: Delete :exec DELETE FROM books WHERE id = $1; -- name: Get :one SELECT * FROM books WHERE id = $1 ;
  21. • Função resultante (one) <Sqlc> func (q *Queries) Get(ctx context.Context,

    id int32) (Book, error) { row := q.db.QueryRow(ctx, get, id) var i Book err := row.Scan( &i.ID, &i.Isbn, &i.Title, &i.Author, &i.Genre, &i.Quantity, &i.PublicizedAt, ) return i, err }
  22. • Implementação presente no pacote testing ◦ Tão simples quanto

    um teste unitário! • Benchmarks e o pprof são essenciais na identificação e resolução de gargalos Não acredite em fanfics. Acredite em métricas! <Benchmarks>
  23. • Acesse o projeto no GitHub: golang-orm-benchmarks ◦ Gorm ◦

    Ent ◦ Bun ◦ Sqlc ◦ SQL puro ▪ database/sql ▪ pgx <Benchmarks>
  24. type Benchmark interface { Init() error Close() error Insert(b *testing.B)

    InsertBulk(b *testing.B) Update(b *testing.B) Delete(b *testing.B) FindByID(b *testing.B) FindPage(b *testing.B) } <Benchmarks> Referência: https://github.com/efectn/go-orm-benchmarks
  25. <Benchmarks> • Execução ◦ make benchmark-insert ◦ make benchmark-insert-bulk ◦

    make benchmark-update ◦ make benchmark-delete ◦ make benchmark-select-one ◦ make benchmark-select-page ◦ make benchmark-all - paciência!
  26. Exemplo da saída da execução do benchmark de insert Operation:

    insert sqlc: 6277 213470 ns/op 288 B/op 8 allocs/op raw: 4900 248244 ns/op 576 B/op 11 allocs/op ent: 6444 244678 ns/op 3928 B/op 91 allocs/op bun: 4496 263463 ns/op 5076 B/op 15 allocs/op pgx: 6474 198450 ns/op 304 B/op 9 allocs/op gorm: 5403 235059 ns/op 7351 B/op 101 allocs/op <Exemplo> • Regras do jogo antes de cada execução ◦ Base de dados sempre limpa ◦ Para avaliar update, delete e selects, o tempo de preparação da base é desconsiderado.
  27. { ALGUÉM NO REDDIT … In my career I've gone

    from "ORM's are essential" through to "ORM's are evil incarnate" to get to my current mindset which is "it depends".
  28. • Atente-se às necessidades do projeto ◦ Sua escolha pode

    ser determinante a médio-longo prazo • Leia a documentação e experimente! • Cuidado benchmarks antigos • Utiliza ORM ao projeto, mas executa Raw Queries o tempo todo? Reflita! <Conclusão>
  29. SELECT * FROM books ORDER BY id DESC OFFSET 100

    LIMIT 10; Tradução: carregue os 110 primeiros registros e depois descarte 100. <Cursor Pagination> Books row 1 row 2 row 3 row 4 row 5 ... row 100 row 101 row 102 row 103 row 104 row 105 row 106 row 107 row 108 row 109 row 110
  30. SELECT * FROM books ORDER BY id DESC OFFSET 110

    LIMIT 10; <Cursor Pagination> Books My new row row 1 row 2 row 3 row 4 row 5 ... row 100 row 101 row 102 row 103 row 104 row 105 row 106 row 107 row 108 row 109 row 110 books My new row row 1 row 2 row 3 ... row 110 (repetição) row 111 row 112 row 113 row 114 row 115 row 116 row 117 row 118 row 119
  31. SELECT * FROM books WHERE id > <last_seen_id> ORDER BY

    id DESC LIMIT 100; Há vida sem offset 🙌 <Cursor Pagination>
  32. SOBRE MIM Engenheira de Software na Toggl. Tenho sintomas diversos

    pré-apresentação que vão de tremedeira nas pernas a taquicardia. Go for everybody! — GopherCon Brasil 2024