Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Escolhendo (ou não) o melhor ORM para o seu projeto <10/05/2024>

Slide 3

Slide 3 text

CONTEÚDO ORMs 01 Projetos em Go Benchmarks Conclusão 02 03 04

Slide 4

Slide 4 text

ORMs

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

● 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

Slide 7

Slide 7 text

● 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

Slide 8

Slide 8 text

Quando pensamos em 5 campos, isso não parece muito. 📚Vale a pena ler: What ORMs have taught me: just learn SQL.

Slide 9

Slide 9 text

● 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

Slide 10

Slide 10 text

Como os ORMs se alinham aos pilares da Linguagem Go?

Slide 11

Slide 11 text

Várias formas de fazer a mesma coisa. Mas isso é bom, não é?

Slide 12

Slide 12 text

Slide 13

Slide 13 text

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

● 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

Slide 16

Slide 16 text

Voltando as mazelas dos ORMs, notou que ainda não falamos sobre Desempenho?

Slide 17

Slide 17 text

Projetos em GO

Slide 18

Slide 18 text

● 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

Slide 19

Slide 19 text

Fonte: https://ossinsight.io/collections/golang-orm/

Slide 20

Slide 20 text

● ⭐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

Slide 21

Slide 21 text

● 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...")

Slide 22

Slide 22 text

Slide 23

Slide 23 text

Slide 24

Slide 24 text

● 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{})

Slide 25

Slide 25 text

● ⭐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

Slide 26

Slide 26 text

Você utiliza offset para fazer paginação? 📚Vale a pena ler: No offset - Use the index, Luke!

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Slide 29

Slide 29 text

● ⭐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

Slide 30

Slide 30 text

● 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

Slide 31

Slide 31 text

Mas… Nem todo o código gerado será utilizado.

Slide 32

Slide 32 text

● ⭐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

Slide 33

Slide 33 text

● Geração de código a partir de queries em tempo de compilação ○ Anotações -- name: Delete :exec DELETE FROM books WHERE id = $1; -- name: Get :one SELECT * FROM books WHERE id = $1 ;

Slide 34

Slide 34 text

● Função resultante (one) 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 }

Slide 35

Slide 35 text

Benchmarks

Slide 36

Slide 36 text

● 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!

Slide 37

Slide 37 text

● Acesse o projeto no GitHub: golang-orm-benchmarks ○ Gorm ○ Ent ○ Bun ○ Sqlc ○ SQL puro ■ database/sql ■ pgx

Slide 38

Slide 38 text

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) } Referência: https://github.com/efectn/go-orm-benchmarks

Slide 39

Slide 39 text

● 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!

Slide 40

Slide 40 text

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 ● 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.

Slide 41

Slide 41 text

Slide 42

Slide 42 text

Slide 43

Slide 43 text

Slide 44

Slide 44 text

Slide 45

Slide 45 text

Slide 46

Slide 46 text

Slide 47

Slide 47 text

Conclusão

Slide 48

Slide 48 text

{ 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".

Slide 49

Slide 49 text

● 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!

Slide 50

Slide 50 text

CONTATo CONECTE-SE COMIGO! /IN/ANDREIACAMILA Força Rio Grande do Sul! Doe e ajude a nossa gente!

Slide 51

Slide 51 text

Slide 52

Slide 52 text

SELECT * FROM books ORDER BY id DESC OFFSET 100 LIMIT 10; Tradução: carregue os 110 primeiros registros e depois descarte 100. 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

Slide 53

Slide 53 text

SELECT * FROM books ORDER BY id DESC OFFSET 110 LIMIT 10; 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

Slide 54

Slide 54 text

SELECT * FROM books WHERE id > ORDER BY id DESC LIMIT 100; Há vida sem offset 🙌

Slide 55

Slide 55 text

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