🔓 Esse artigo é o quarto de uma sequência de estudo em andamento.
Documento
Um documento é qualquer registro de informações, independentemente do formato ou suporte utilizado para registrá-las.Fonte: Wikipédia
O texto abaixo é referente à branch feature/initial-main-000 do repositório de estudo. Ao final do artigo teremos desenvolvido até o ponto da branch feature/catalog-service-001
📚 Diagramas, ADRs e Documentação
Um bom inicio para microsserviços é um diagrama simples, um Diagrama de Fluxo de Processo, onde podemos definir as ações e quais entidades do sistema elas afetam.
Existem inúmeras ferramentas que permitem gerar diagramas, inclusive de maneira colaborativa, Miro Board, Draw.io etc... mas a abordagem de arquivos readme.md
ricos (os quais falarei em outro artigo) permite que documentação sobre códigos fique próxima do programador, mesmo existindo Wikis, GithubPages ou Coda do projeto. O Mermaid que gera diagramas a partir de código, posteriormente renderizados direto de arquivos markdown no GitHub, foi a escolha.
Documentação próxima ao código "induz" (ou deveria "induzir") o desenvolvedor a mantê-la atualizada, isso poderia ser parte da sua Definição de Pronto pois:
"Documentação é um dos principios de disponibilidade." - Susan Fowler
O problema da "sala de cinema" já é "velho conhecido" para aqueles que estudaram UML, mesmo que esse não seja seu caso, é simples compreender como as coisas se relacionam.
"Nada é mais didático do que desenhar para comunicar." - Bruno Nardini
A partir da iteração desse fluxo, similar ao caso de uso, fica fácil entender que um Employee User
tem as responsabilidades de manutenção de nosso catálogo de filmes
, salas de cinema
, Endereços
, sessões
, assentos
e nosso produto de fato, o ticket
. Nada mais do que um assento de uma sala de cinema que está reservado para o horário x
de uma sessão.
A partir da dinâmica representada pelo diagrama anterior e com algum conhecimento de base de dados relacionais avancei em direção ao Diagrama de Entidade-Relacionamento (ER) abaixo:
Isso nos dá sólidas garantias de que todos os envolvidos compreendam o que será feito. Mas e quanto a como será feito? Quais as decisões de arquitetura serão tomadas para que o projeto de prosseguimento? Para isso uma boa prática e definirmos essas decisões de arquitetura em outro documento e não em nosso readme rico. Vamos definir uma ADR - Architectural Decision Records
Deixo aqui o contexto com as principais decisões do documento, mas sugiro a você dar uma olhada no ADR completo no repositório.
"Estamos projetando um Microsserviço de Catálogo de ingressos de cinema. Decidimos usar o framework Express com TypeScript para criar a API em arquitetura MVC. Faremos testes de unidade e integração com Jest e usaremos Eslint para linting. Adotaremos principios SOLID onde se aplicar. O ORM Sequelize será usado para acessar o banco de dados PostgreSQL. Utilizaremos Swagger para documentação da API. O desenvolvimento acontecerá em um Monorepo NX, permitindo a organização de múltiplos projetos em um único repositório. Ceph será usado usado como storage local para os posters, simulando um Amazon S3. Não integraremos autenticação agora, mas consideramos Keycloak no futuro. Cache Redis está sendo considerado para o futuro"
👷 Mãos à Obra ou Show-me the code
Agora as coisas vão ficar um pouco mais sérias. Estou partindo do pressuposto de que o leitor possui alguma familiaridade com o VSCode
e o terminal
. Nosso ambiente é padronizado e o gerenciador de monorepos NX nos permite um conjunto de facilidades para criar e manter nossos microsserviços
, pacotes dentro de do seu diretorio packages
. Para utilizar esse poder, temos que possuir a última versão estável do Node.js instalada e do gerenciador de Monorepos NX
Logo após, instalar nosso gerenciador de Monorepo.
Pronto, com isso agora podemos finalmente por a "Mão na Massa" e codar algo que seja aderente as nossas guidelines.
Navegue Até a raiz do projeto em seu terminal, para instalar suas dependencias. Digite o seguinte comando:
$ npm install
Com isso, teremos as dependências do projeto disponíveis em nosso monorepo NX. Agora, podemos criar nosso primeiro microserviço. Como estamos em um ambiente JavaScript, o próprio gerenciador nos auxiliará nessa tarefa. Basta digitar:
$ nx generate @nrwl/express:app catalog-service
Agora sua pasta packages vai se parecer com a estrutura abaixo:
$ tree
.
└── packages
| ├── catalog-service
| ├── catalog-service-e2e
| ├── cine-ticket-front-site
| └── cine-ticket-nginx
.
O NX criou dois novos packages: catalog-service
já com um backend mínimo e catalog-service-e2e
, seus respectivos testes e2e. Mas como o projeto esta configurado para typescript, o arquivo packages/catalog-service/src/main.ts
acusa erro na linha 13
por conta das definições do typescript e pela declaração da variável req
que não esta sendo utilizada, basta substituir por _
que o código estará correto.
Agora rode o seguinte comando para testar seu "microsserviço mínimo":
$ nx serve catalog-service
A saída deve ser similar a:
chunk (runtime: main) main.js (main) 1.87 KiB [entry] [rendered]
webpack compiled successfully (eb148a07024bd3a0)
Debugger listening on ws://localhost:9229/75f60138-7e65-4fbb-95b9-23c6553bfdf8
For help, see: https://nodejs.org/en/docs/inspector
Listening at http://localhost:3333/api
Acesando a url que esta sendo servida, Listening
, no comando o seu browser deve ser algo parecido com isso:
Embora pareça estar tudo funcional, percebi que os arquivos transpilados que devem ser distribuídos, os dist
, estavam sendo gerados na raiz do projeto e gostaria que nesse momento eles fossem gerados dentro de seus respectivos pacakges
.
A solução foi simples, bastando acessar o arquivo /packages/catalog-service/project.json
e alterar o valor de build.options.outputPath
para packages/catalog-service/dist
e resolvido. Interrompa o processo do catalog-service
digitando ctrl + c
no terminal do seu VSCode
, remova a pasta dist
da raiz e rode o comando $ nx serve catalog-service
novamente. A pasta dist deve ser criada dentro do catalog-service
agora, o comportamento esperado.
💱 Trocando WebPack por ESbuild
Por padrão, o NX usa o WebPack para gerar seus builds. No entanto, optei por uma abordagem mais moderna e adotei o EsBuild para essa tarefa. Precisaremos instalar suas dependências e fazer algumas configurações adicionais. Na raiz do projeto, execute o seguinte comando:
$ npm install --save-dev @nrwl/esbuild
$ npm install --save-dev @nx/esbuild
E então, devemos informar ao NX que o nosso pacote catalog-microsservice
deve agora utilizar as configurações do esbuild
. Você pode fazer isso com o seguinte comando:
$ nx g @nx/esbuild:configuration catalog-service \
--main=packages/catalog-service/src/main.ts \
--tsConfig=packages/catalog-service/tsconfig.custom.json --skipValidation
Mas não é apenas isso; na verdade, serão necessários mais dois ajustes. Para isso, abra o arquivo package.json
que deve ter sido criado na raiz de packages/catalog-microsservice
. Ele deve se parecer com a estrutura vista abaixo:
{
"name": "@cine-ticket-microsservices/catalog-service",
"version": "0.0.1"
}
Abaixo do campo "version": "0.0.1",
coloque a seguinte entrada "type": "module"
no documento json informando ao Node.js que os arquivos JavaScript no projeto devem ser tratados como módulos ESM. Isso significa que podemos usar a sintaxe import
e export
para carregar módulos em vez da sintaxe require e module.exports usada em módulos CommonJS.
Para finalizar, remova os comentários e as linhas desnecessárias do seu arquivo packages/catalog-microsservice/main.ts
. Isso inclui as linhas de 1
a 5
, o import de path
na linha 7
e a linha 11
, já que não faremos uso de assets
.
packages/catalog-microsservice/main.ts
1 /**
2 * This is not a production server yet!
3 * This is only a minimal backend to get started.
4 */
5
6 import express from 'express';
7 import * as path from 'path';
8
9 const app = express();
10
11 app.use('/assets', express.static(path.join(__dirname, 'assets')));
12
13 app.get('/api', (_, res) => {
14 res.send({ message: 'Welcome to catalog-microsservice!' });
15 });
16
17 const port = process.env.PORT || 3333;
18 const server = app.listen(port, () => {
19 console.log(`Listening at http://localhost:${port}/api`);
20 });
21 server.on('error', console.error);
Pronto, mudamos nosso builder e tudo deve continuar como era antes.
🎲 Banco de Dados, Models e o Serviço a partir dai
Temos um serviço mínimo rodando; é hora de começar a construir funcionalidades reais e robustas. Vamos dar vida às entidades e relações que mapeamos no Diagrama ER a partir do banco de dados. No terminal, na raiz do projeto, saia do prompt do nx serve
digitando ctrl + c
e digite os comandos para criar a pasta onde armazenaremos os dados de volume de nosso banco de dados
mkdir packages/catalog-service-db
mkdir packages/catalog-service-db/data
Adicione as seguintes linhas no seu docker-compose.yml
visando criar o banco de dados Postgres que nosso microsservico Utilizará.
docker-compose.yml
catalog-service-db:
image: postgres:latest
container_name: catalog-service-db
restart: always
environment:
POSTGRES_DB: catalog_service_db
POSTGRES_USER: catalog_service_user
POSTGRES_PASSWORD: catalog_service_password
ports:
- "5432:5432"
volumes:
- ./packages/catalog-service-db/data:/var/lib/postgresql/data
networks:
- cine-ticket-network
Rode a partir da raiz um docker compose up --build
para subir e reconstruir as imagens de nossos serviços em conjunto com o Postgres, isso deve demorar um pouco. Note que ainda não configuramos o catalog-service
para subir junto, apenas o seu banco.
Agora que temos um banco de dados, vamos criar a conexão e os modelos correspondentes aos nossos dados, conforme decidido no ADR. Abra um novo terminal e crie a pasta /packages/catalog-service/src/models
executando o seguinte comando em um novo terminal:
mkdir packages/catalog-service-db/src/models
Sua árvore de diretório deve ficar similar a:
$ tree
.
└── packages
| ├── catalog-service
| ├── src
| ├── assets
| ├── models
.
E então, instalaremos as dependências necessárias para criar nossos modelos. Em uma nova janela do terminal insira os comandos:
npm install sequelize pg pg-hstore
npm install @types/sequelize --save-dev
- sequelize: ORM (Object-Relational Mapping) que você usará para interagir com o banco de dados.
- pg: driver do PostgreSQL para o Node.js, permitindo que o Sequelize se conecte ao PostgreSQL.
- pg-hstore: pacote que lida com o mapeamento de tipos de dados JSON para o PostgreSQL, usado pelo Sequelize.
- types/sequelize: fornece definições de tipos TypeScript para a biblioteca Sequelize
✍️ Nota:
Tentei usar o sequelize-cli para automatizar a geração das models, mas ele ainda não oferece suporte ao TypeScript. Portanto, criei as models manualmente. Existe um pacote chamado sequelize-typescript, mas como seu mantenedor não é a equipe oficial do Sequelize, não o utilizei.
🔌 Criando a conexao:
touch packages/catalog-service/src/models/db.ts
code packages/catalog-service/src/models/db.ts
Esses comandos criam e abrem no seu VsCode
o arquivo packages/catalog-service/models/db.ts
que vai conectar nossa API
ao banco, veja abaixo uma Implantação simples de conexão ao banco que você deve digitar no arquivo:
packages/catalog-service/models/db.ts
1 import { Sequelize } from 'sequelize';
2
3 const pool = {
4 max: 5,
5 min: 0,
6 acquire: 130000,
7 idle: 10000
8 };
9
10 const sequelize = new Sequelize({
11 dialect: 'postgres',
12 host: 'localhost',
13 database: 'catalog_service_db',
14 username: 'catalog_service_user',
15 password: 'catalog_service_password',
16 pool
17 });
18
19 export default sequelize;
✍️ Nota:
Atenção especial entre as linhas3
a8
. Esse é um problema clássico para iniciantes e aborda uma das "Falácias" - a da "Composição" - que afirma: "se um componente individual funciona bem, todos eles funcionarão bem em conjunto". Sua aplicação não escala em conjunto com seu banco; é necessário que tenhamos um pool de conexões que evite gargalos. Já vi isso acontecer em uma aplicação na qual microserviços individuais eram bem otimizados, mas os gargalos frequentemente ocorriam devido a bloqueios no banco de dados central, limitando a escalabilidade horizontal.
Instanciamos com os parâmetros de conexão e exportamos o sequelize
.
Com isso efetivamente vinculamos o banco de dados incluído em nosso docker-compose.yml
ao projeto. Como software gerenciador de banco de dados, estou utilizando o DBeaver e sugiro sua instalação.
Com o DBeaver instalado, crie uma nova conexão nele usando os mesmos dados de acesso usados em nosso arquivo db.ts
para verificar o banco, mesmo que não tenhamos as tabelas ainda:
Vamos agora criar nossa primeira entidade "filme", estando na raiz do projeto digite os seguintes comandos:
$ touch packages/catalog-service/src/models/film.ts
$ code packages/catalog-service/src/models/film.ts
Criamos e abrimos no nosso vscode a entidade filme, a qual digitaremos o seguinte código typescript baseado na entidade film
de nosso DER:
packages/catalog-service/models/film.ts
1 import { Model, DataTypes, Sequelize } from 'sequelize';
2
3 class Film extends Model {
4 public id!: number;
5 public uuid!: string;
6 public name!: string;
7 public description!: string;
8 public age_rating!: number;
9 public subtitled!: boolean;
10 public poster!: string;
11
12 static associate() {
13 //static associate(models: any) {
14 // Define as associações aqui...
15 }
16 }
17
18 export default (sequelize: Sequelize) => {
19 Film.init({
20 id: {
21 type: DataTypes.INTEGER,
22 autoIncrement: true,
23 primaryKey: true,
24 },
25 uuid: {
26 type: DataTypes.UUID,
27 allowNull: false,
28 },
29 name: {
30 type: DataTypes.STRING(500),
31 allowNull: false,
32 },
33 description: {
34 type: DataTypes.TEXT,
35 allowNull: false,
36 },
37 age_rating: {
38 type: DataTypes.INTEGER,
39 allowNull: false,
40 },
41 subtitled: {
42 type: DataTypes.BOOLEAN,
43 allowNull: false,
44 },
45 poster: {
46 type: DataTypes.STRING(500),
47 allowNull: false,
48 },
49 }, {
50 sequelize,
51 modelName: 'Film',
52 tableName: 'film',
53 });
54
55 return Film;
56 };
57
Perceba que na linha 3
, estendemos a classe Model
do Sequelize
ao definir nossa classe Film
. Estamos "abertos para extensão e fechados para modificação". Dentro do código de definição da classe, informamos que nossos atributos são públicos, seguidos pelo nome do atributo e pelo caractere de exclamação !
, indicando que não podem receber valores null
e nem undefined
, e também indica que os valores dos atributos serão definidos após a criação do objeto. Nessa classe, na linha 22
, também são definidas as associações que Film
terá com outras entidades. Como ela é única no momento, nenhuma associação foi definida.
A partir da linha 18
, estamos exportando nossa entidade Film
e descrevendo os tipos referentes aos DataTypes
do Sequelize
a quais esses atributos pertencem.
Retorne ao seu arquivo packages/catalog-service/models/db.ts
e adicione o import da nossa entidade film
. Em seguida, crie uma instância dela, passando o objeto sequelize
, como pode ser visto nas linhas 2
e 22
abaixo:
packages/catalog-service/models/db.ts
1 import { Sequelize } from 'sequelize';
2 import Film from './film';
3
4 const pool = {
5 max: 5,
6 min: 0,
7 acquire: 130000,
8 idle: 10000
9 };
10
11 const sequelize = new Sequelize({
12 dialect: 'postgres',
13 host: 'localhost',
14 database: 'catalog_service_db',
15 username: 'catalog_service_user',
16 password: 'catalog_service_password',
17 pool
18 });
19
20 Film(sequelize);
21
22 export default sequelize;
23
Com isso a entidade esta quase pronta para ser espelhada em nosso banco, para tanto um utltimo ajuste em nosso packages\catalog-microsservice\src\main.ts
sera necessario:
packages\catalog-microsservice\src\main.ts
1 import express from 'express';
2 import sequelize from './models/db';
3
4 const app = express();
5
6 app.get('/api', (_, res) => {
7 res.send({ message: 'Welcome to catalog-microsservice!' });
8 });
9
10 const port = process.env.PORT || 3333;
11 const server = app.listen(port, () => {
12 console.log(`Listening at http://localhost:${port}/api`);
13 });
14 server.on('error', console.error);
15
16 async function syncDB() {
17 await sequelize.sync();
18 console.log('Models synchronized with the database');
19 }
20
21 syncDB();
22
Fizemos o import
do sequelize
que acabamos de configurar na linha 2. Entre as linhas 16
a 21
, criamos uma função assíncrona com a finalidade de criar as tabelas definidas como entidades em nosso packages/catalog-microsservices/src/models
. Atualmente, apenas film
está ali.
Insira o comando para iniciar nossa API
novamente, utilizando o comando nx serve catalog-service
. Devemos obter um resultado semelhante a este:
$ nx serve catalog-service
> nx run catalog-service:serve:development
[ watch ] build succeeded, watching for changes...
Debugger listening on ws://localhost:9229/a5fe752f-36bd-469c-9361-24432ba30c1a
For help, see: https://nodejs.org/en/docs/inspector
Listening at http://localhost:3333/api
Executing (default): SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'film'
Executing (default): CREATE TABLE IF NOT EXISTS "film" ("id" SERIAL , "uuid" UUID NOT NULL, "name" VARCHAR(500) NOT NULL, "description" TEXT NOT NULL, "age_rating" INTEGER NOT NULL, "subtitled" BOOLEAN NOT NULL, "poster" VARCHAR(500) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'film' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Models synchronized with the database
Isso indica que seu modelo/entidade foi sincronizado com o banco que subimos com o docker-compose up --build
que fizemos no inicio do artigo, agora se revisitarmos o DBeaver
e atualizarmos sua visualização clicando F5
com nosso database selecionado, veremos a tabela criada por nosso ultimo comando.
Não deverá ser um fator complicador criar as outras entidades a partir do que já foi exposto aqui e utilizando o DER como guideline. Sugiro tentar mesmo sem os relacionamentos no momento para praticar e só depois olhar o gabarito em forma de lista abaixo:
packages/catalog-service/src/models/room.ts
// Importe os módulos necessários do Sequelize
import { Model, DataTypes, Sequelize } from 'sequelize';
// Defina a entidade "Room"
class Room extends Model {
public id!: number;
public uuid!: string;
public name!: string;
public capacity!: number;
// Defina as associações
static associate(models: any) {
// Uma sala pode ter várias sessões
Room.hasMany(models.Session, { foreignKey: 'room_id' });
// Uma sala pode ter apenas um endereço
Room.belongsTo(models.Address, {
foreignKey: 'address_id',
});
}
}
// Exporte a função que define a entidade e suas configurações
export default (sequelize: Sequelize) => {
Room.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
capacity: {
type: DataTypes.INTEGER,
allowNull: false,
},
}, {
sequelize,
modelName: 'Room',
tableName: 'room',
});
return Room;
};
packages/catalog-service/src/models/session.ts
// Importe os módulos necessários do Sequelize
import { Model, DataTypes, Sequelize } from 'sequelize';
// Defina a entidade "session"
class Session extends Model {
public id!: number;
public uuid!: string;
public film_id!: number;
public room_id!: number;
public description!: string;
public date!: Date;
public start_time!: Date;
public end_time!: Date;
public time!: string;
// Defina as associações
static associate(models: any) {
// Uma sessão pertence a um filme
Session.belongsTo(models.Film, { foreignKey: 'film_id' });
// Uma sessão ocorre em uma sala
Session.belongsTo(models.Room, { foreignKey: 'room_id' });
// Uma sessão pode ter vários tickets
Session.hasMany(models.Ticket, { foreignKey: 'session_id' });
}
}
// Exporte a função que define a entidade e suas configurações
export default (sequelize: Sequelize) => {
Session.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
film_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
room_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: true,
},
date: {
type: DataTypes.DATEONLY,
allowNull: false,
},
start_time: {
type: DataTypes.TIME,
allowNull: false,
},
end_time: {
type: DataTypes.TIME,
allowNull: false,
},
time: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
sequelize,
modelName: 'Session',
tableName: 'session',
});
return Session;
};
packages/catalog-service/src/models/seat.ts
import { DataTypes, Model, Sequelize } from 'sequelize';
class Seat extends Model {
public id!: number;
public uuid!: string;
public room_id!: number;
public code!: string;
static associate(models: any) {
Seat.belongsTo(models.Room, {
foreignKey: 'room_id',
as: 'room',
});
Seat.hasMany(models.Ticket, {
foreignKey: 'seat_id',
as: 'tickets',
});
}
}
export default (sequelize: Sequelize) => {
Seat.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
room_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
code: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'Seat',
tableName: 'seat',
}
);
return Seat;
};
packages/catalog-service/src/models/ticket.ts
import { DataTypes, Model, Sequelize } from 'sequelize';
class Ticket extends Model {
public id!: number;
public uuid!: string;
public session_id!: number;
public seat_id!: number;
static associate(models: any) {
Ticket.belongsTo(models.Session, {
foreignKey: 'session_id',
as: 'session',
});
Ticket.belongsTo(models.Seat, {
foreignKey: 'seat_id',
as: 'seat',
});
}
}
export default (sequelize: Sequelize) => {
Ticket.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
session_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
seat_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
sequelize,
modelName: 'Ticket',
tableName: 'ticket',
}
);
return Ticket;
};
packages/catalog-service/src/models/address.ts
import { DataTypes, Model, Sequelize } from 'sequelize';
class Address extends Model {
public id!: number;
public uuid!: string;
public country!: string;
public state!: string;
public zip_code!: string;
public telephone!: string;
public description!: string;
public postal_code!: string;
public name!: string;
static associate(models: any) {
Address.belongsToMany(models.Room, {
through: models.RoomAddress,
foreignKey: 'address_id',
as: 'rooms',
});
models.Room.belongsToMany(Address, {
through: models.RoomAddress,
foreignKey: 'room_id',
as: 'addresses',
});
}
}
export default (sequelize: Sequelize) => {
Address.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
country: {
type: DataTypes.STRING,
allowNull: false,
},
state: {
type: DataTypes.STRING,
allowNull: false,
},
zip_code: {
type: DataTypes.STRING,
allowNull: false,
},
telephone: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: true,
},
postal_code: {
type: DataTypes.STRING,
allowNull: true,
},
name: {
type: DataTypes.STRING,
allowNull: true,
},
},
{
sequelize,
modelName: 'Address',
tableName: 'address',
}
);
return Address;
};
packages/catalog-service/src/models/roomAddress.ts
import { DataTypes, Model, Sequelize } from 'sequelize';
class RoomAddress extends Model {
public id!: number;
public uuid!: string;
public room_id!: number;
public address_id!: number;
static associate(models: any) {
RoomAddress.belongsTo(models.Room, {
foreignKey: 'room_id',
as: 'room',
});
RoomAddress.belongsTo(models.Address, {
foreignKey: 'address_id',
as: 'address',
});
}
}
export default (sequelize: Sequelize) => {
RoomAddress.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
uuid: {
type: DataTypes.UUID,
allowNull: false,
},
room_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
address_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
sequelize,
modelName: 'RoomAddress',
tableName: 'roomAddress',
}
);
return RoomAddress;
};
Por fim, iremos criar as novas tabelas ao "injetar" a instancia do sequelize, que abstrai nossa conexao, nas instancias das models/entidades recem criadas no nosso packages/catalog-microsservice/db.ts
import { Sequelize } from 'sequelize';
import Film from './film';
import Session from './session';
import Room from './room';
import Seat from './seat';
import Ticket from './ticket';
import Address from './address';
import RoomAddress from './roomAddress';
const pool = {
max: 5,
min: 0,
acquire: 130000,
idle: 10000
};
const sequelize = new Sequelize({
dialect: 'postgres',
host: 'localhost',
database: 'catalog_service_db',
username: 'catalog_service_user',
password: 'catalog_service_password',
pool
});
Film(sequelize);
Session(sequelize);
Room(sequelize);
Seat(sequelize);
Ticket(sequelize);
Address(sequelize);
RoomAddress(sequelize);
export default sequelize;
Ao se criar essas models e criar o vínculo em nosso packages/catalog-microsservice/src/models/db.ts
e rodar novamemte o comando $ nx serve catalog-service
devemos ter a seguinte estrutura de tabelas criadas em nosso postgres:
🤓 Conclusão (por hora)
Vimos a importância da criação de diagramas colaborativos para iniciar o projeto e demos uma olhada em um ADR
simples que definia nossas escolhas técnicas e arquiteturais.
Em um projeto da vida real, a partir dessas documentações, expandiríamos nosso projeto em direção à criação de tarefas menores, issues
no GitHub
ou tasks
no Jira
, para que pudéssemos desenvolver. Mas aqui, por finalidade puramente didática, já colocamos a mão na massa para criar um projeto mínimo utilizando as facilidades do NX
. Criamos o microsserviço de catálogo, atualizamos o seu builder
e a partir daí criamos nosso banco, conexão e entidades.
Não falamos ainda de testes, embora tenha a forte convicção de que devemos seguir a abordagem TDD (Test-Driven Development) neste projeto. Do meu ponto de vista, não há nenhuma lógica de negócio que valha a pena ser testada unitariamente no momento.
👣 Próximos passos
Vamos continuar seguindo na produção do nosso catálogo. Agora, com as entidades já relacionadas, iremos construir rotas dentro do padrão Swagger, respeitando o REST, e a partir daí criar uma suíte de testes que garantam as operações dos nossos endpoints.
Continue por aqui, pois o projeto só tende a melhorar!
👈 Anterior | Próximo 👉 |
---|---|
Monorepo & Docker | [Em Breve] |
Top comments (0)