Limites de Esquema Orientados a Domínio
Contextos delimitados mapeados para esquemas PostgreSQL oferecem isolamento de namespace sem o custo operacional de bancos de dados separados.
Receita
Cartão de referência rápida - pronto para copiar e colar.
CREATE SCHEMA identity;
CREATE SCHEMA billing;
CREATE SCHEMA catalog;
REVOKE ALL ON SCHEMA billing FROM PUBLIC;
GRANT USAGE ON SCHEMA billing TO billing_app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA billing TO billing_app_role;
CREATE TABLE identity.users (
user_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL UNIQUE
);
CREATE TABLE billing.subscriptions (
subscription_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id uuid NOT NULL, -- referência lógica; FK opcional entre contextos
plan_code text NOT NULL
);Quando usar isso:
- Múltiplas equipes compartilham um cluster PostgreSQL, mas possuem domínios diferentes.
- Você precisa de propriedade mais clara do que um esquema
publicplano fornece. - O acoplamento entre contextos deve ser explícito (FK, evento ou API) em vez de junções acidentais.
Exemplo de Trabalho
BEGIN;
CREATE SCHEMA IF NOT EXISTS identity;
CREATE SCHEMA IF NOT EXISTS catalog;
CREATE SCHEMA IF NOT EXISTS fulfillment;
CREATE ROLE storefront_app LOGIN PASSWORD 'replace-me';
CREATE ROLE fulfillment_app LOGIN PASSWORD 'replace-me';
CREATE TABLE identity.customers (
customer_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text NOT NULL UNIQUE,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE catalog.products (
product_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku text NOT NULL UNIQUE,
name text NOT NULL,
price numeric(12, 2) NOT NULL CHECK (price >= 0)
);
CREATE TABLE fulfillment.orders (
order_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id uuid NOT NULL,
product_id bigint NOT NULL REFERENCES catalog.products (product_id),
quantity integer NOT NULL CHECK (quantity > 0),
status text NOT NULL DEFAULT 'pending',
created_at timestamptz NOT NULL DEFAULT now()
);
-- FK entre contextos apenas onde o acoplamento é intencional
-- identity.customers é referenciado logicamente por customer_id (sem FK para evitar acoplamento rígido)
REVOKE ALL ON SCHEMA identity, catalog, fulfillment FROM PUBLIC;
GRANT USAGE ON SCHEMA identity, catalog TO storefront_app;
GRANT SELECT, INSERT, UPDATE ON identity.customers TO storefront_app;
GRANT SELECT ON catalog.products TO storefront_app;
GRANT INSERT ON fulfillment.orders TO storefront_app;
GRANT USAGE ON SCHEMA catalog, fulfillment TO fulfillment_app;
GRANT SELECT ON catalog.products TO fulfillment_app;
GRANT SELECT, UPDATE ON fulfillment.orders TO fulfillment_app;
ALTER ROLE storefront_app SET search_path = identity, catalog, fulfillment, public;
ALTER ROLE fulfillment_app SET search_path = fulfillment, catalog, public;
COMMIT;O que isso demonstra:
- Um esquema por contexto delimitado (
identity,catalog,fulfillment). GRANTbaseado em função limita qual aplicativo pode escrever em quais tabelas.search_pathpor função mantém nomes não qualificados previsíveis.- Referência entre contextos via
customer_idsem uma FK rígida quando os contextos evoluem independentemente.
Mergulho Profundo
Como Funciona
- Esquemas PostgreSQL são namespaces dentro de um banco de dados - não servidores separados.
GRANT USAGE ON SCHEMAcontrola a visibilidade; privilégios de tabela controlam leitura/escrita.ALTER ROLE ... SET search_pathevita prefixoscatalog.productsno SQL do aplicativo quando desejado.- FKs entre esquemas são suportados, mas criam acoplamento de implantação - migrações em um contexto podem bloquear outro.
Padrões de Limite de Contexto
| Padrão | Acoplamento | Quando |
|---|---|---|
| Esquema por contexto | Baixo-Médio | Cluster compartilhado, equipes distintas |
| FK entre esquemas | Médio-Alto | Consistência forte necessária |
| Apenas referência de ID | Baixo | Contextos versionados independentemente |
| Tabela de outbox/evento | Baixo | Integração assíncrona preferida |
Notas SQL
-- Lista tabelas por esquema
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY 1, 2;
-- Move uma tabela mal colocada para o contexto correto
ALTER TABLE public.invoices SET SCHEMA billing;Armadilhas
- Tudo em
public- equipes sobrescrevem as convenções de nomenclatura umas das outras. Correção: crie esquemas de domínio desde o primeiro dia. - Espaguete de FKs entre esquemas - uma falha de migração bloqueia todos os contextos. Correção: prefira IDs lógicos mais validação de aplicativo ou consistência eventual.
GRANTexcessivamente amplo -GRANT ALL ON SCHEMA public TO appexpõe tabelas de catálogo para faturamento. Correção: concessões de menor privilégio por função.search_pathimplícito -SELECT * FROM productsnão qualificado atinge o esquema errado. Correção: definasearch_pathpor função ou sempre qualifique os nomes.- Sequências compartilhadas entre contextos - colisões acidentais de ID em logs. Correção: nomes de tabela com prefixo de contexto e colunas de identidade separadas.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Banco de dados por serviço | Isolamento forte, escala independente | Equipe pequena, altas necessidades de relatórios de junção cruzada |
| Esquema único + prefixo de nome | Aplicativos minúsculos, uma equipe | Múltiplas equipes com nomes de tabela conflitantes |
| Multi-tenancy em nível de linha | Isolamento de inquilino SaaS | Propriedade de domínio distinta (não isolamento de inquilino) |
| Esquema de integração materializado | Modelos de leitura cruzando contextos | Você pula a definição de regras de propriedade de escrita |
FAQs
Um esquema PostgreSQL é o mesmo que um contexto delimitado DDD?
É um mapeamento prático, não perfeito. Um contexto delimitado é um limite linguístico e de propriedade; um esquema é uma ferramenta de namespace. Alinhe-os quando uma equipe possuir um contexto.
Devo usar chaves estrangeiras entre esquemas?
Use-as quando ambos os contextos implantarem juntos e a consistência forte for importante. Pule-as quando as equipes enviarem migrações em cadências independentes - use referências UUID e trabalhos de reconciliação em vez disso.
Como evito SELECT entre contextos em SQL ad-hoc?
Revogue SELECT em esquemas estrangeiros das funções do aplicativo. Dê aos analistas uma função somente leitura com acesso mais amplo em uma réplica.
Um serviço pode possuir múltiplos esquemas?
Sim - uma equipe de plataforma pode possuir identity e access_control. Evite um esquema possuído por muitos serviços; isso recria o caos de public.
O que vai para o esquema de integração ou relatórios?
Tabelas denormalizadas, visualizações materializadas e consumidores de outbox - escritos por trabalhos em lote, lidos por BI. Não deixe serviços OLTP escreverem lá diretamente.
Como funcionam as migrações por esquema?
Ferramentas como Flyway podem usar schemas = billing por tabela de histórico de migração. Mantenha pastas de migração separadas por contexto delimitado no git.
Os contextos devem compartilhar tipos enum?
Duplicar text + CHECK por contexto reduz o acoplamento. Tipos ENUM compartilhados prendem os cronogramas de migração juntos.
Como isso interage com SaaS multi-inquilino?
O isolamento de inquilino (RLS, tenant_id) é ortogonal. Você pode ter um esquema billing com tenant_id em cada tabela - veja Padrões Multi-Inquilino.
Qual é um bom primeiro passo a partir de um esquema público plano?
CREATE SCHEMA legacy;
ALTER TABLE public.old_invoices SET SCHEMA legacy;Mova tabelas em grupos por equipe, depois reforce as concessões.
Como documentar os limites de contexto?
Use COMMENT ON SCHEMA mais um ADR listando proprietários, chamadas permitidas entre contextos e junções proibidas.
Relacionados
- Noções Básicas de Modelagem de Dados - exemplo de agrupamento de esquemas
- ADR: Banco de Dados Único vs Banco de Dados por Serviço - quando os esquemas não são suficientes
- Estratégia de Evolução de Esquema - contratos em evolução entre contextos
- Esquema Compartilhado + tenant_id - isolamento de inquilino dentro de um contexto
Versões de Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x e PostGIS 3.5+.