Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
    B --> C["Controller / Handler"]
    C --> D["Service Layer (Regras de Negócio)"]
    D --> E["Repository / ORM (Persistência)"]
    E --> F["Banco de Dados / Cache"]

    subgraph ARQ["Arquitetura do Capítulo"]
        G["Conceito: Relacionamentos entre Tabelas (Relationship) e Consultas com Join"]
        H["Segurança, Validação e Resiliência"]
        I["Alta Performance e Escalabilidade"]
    end

    D --> ARQ

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px

🏛️ 1. Foreign Key vs. Relationship: dois conceitos distintos

Em SQLModel, a foreign key é a coluna real do banco que garante integridade referencial no nível SQL — sem ela, o banco aceitaria um Livro apontando para um Autor inexistente. Já Relationship() é um atributo puramente do lado Python/SQLAlchemy: não gera coluna nem existe no SELECT a menos que seja explicitamente navegado; ele instrui o ORM sobre como carregar objetos relacionados em memória.

from typing import List, Optional
from sqlmodel import SQLModel, Field, Relationship

class Autor(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    nome: str
    livros: List["Livro"] = Relationship(back_populates="autor")

class Livro(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    titulo: str
    autor_id: int = Field(foreign_key="autor.id")
    autor: Optional[Autor] = Relationship(back_populates="livros")

back_populates mantém os dois lados sincronizados em memória: atribuir livro.autor = a também atualiza a.livros automaticamente, sem consultas adicionais, desde que os objetos já estejam carregados na mesma sessão.

Muitos-para-muitos com tabela de ligação

Relacionamentos N:N exigem uma tabela intermediária explícita — SQLModel não a gera magicamente:

class UsuarioPerfilLink(SQLModel, table=True):
    usuario_id: Optional[int] = Field(default=None, foreign_key="usuario.id", primary_key=True)
    perfil_id: Optional[int] = Field(default=None, foreign_key="perfil.id", primary_key=True)

class Perfil(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    nome: str
    usuarios: List["Usuario"] = Relationship(back_populates="perfis", link_model=UsuarioPerfilLink)

class Usuario(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    nome: str
    perfis: List[Perfil] = Relationship(back_populates="usuarios", link_model=UsuarioPerfilLink)

A tabela de ligação tem chave primária composta pelas duas foreign keys — cada linha representa uma associação, e o par (usuario_id, perfil_id) nunca se repete.

O problema N+1 e como evitá-lo

Acessar autor.livros de forma “preguiçosa” (lazy loading) dentro de um loop — por exemplo, ao serializar uma lista de 50 autores e, para cada um, ler .livros — dispara uma consulta SQL adicional por autor: 1 consulta inicial + N consultas subsequentes. Em contexto assíncrono isso é ainda mais grave, porque o lazy loading implícito não funciona de forma transparente com AsyncSession (pode lançar MissingGreenlet) e precisa ser evitado deliberadamente com eager loading:

from sqlalchemy.orm import selectinload
from sqlmodel import select

async def listar_autores_com_livros(session):
    stmt = select(Autor).options(selectinload(Autor.livros))
    resultado = await session.exec(stmt)
    return resultado.all()

selectinload executa uma segunda consulta única com WHERE autor_id IN (...) para todos os autores retornados — 2 consultas no total, independente de N. A alternativa joinedload faz tudo em uma única consulta com LEFT OUTER JOIN, mas duplica linhas do lado “um” quando a relação “muitos” tem múltiplos registros, o que pode ser menos eficiente para coleções grandes. Para junções explícitas e projeções tabulares (não navegação de objetos), usa-se .join() diretamente na consulta:

stmt = (
    select(Autor.nome, Livro.titulo)
    .join(Livro, Livro.autor_id == Autor.id)
)

🔗 Recursos Pedagógicos do Capítulo 07

Recurso Didático Finalidade Link de Acesso
📊 Slides de Aula Apresentação visual interativa com Dark Mode e suporte a teclado Ver Slides
🧠 Quiz Formativo Teste interativo de fixação com feedback imediato por alternativa Fazer Quiz
💻 Exemplos de Código Demonstrações funcionais com código executável Ver Exemplos
🧩 Exercícios em 4 Níveis Lista progressiva de fixação com gabarito em bloco colapsável Resolver Exercícios

⬅️ Capítulo Anterior 📚 Sumário de Tópicos Próximo Capítulo ➡️