04 - Estudo de Microsserviços ingresso.x - O Catálogo
Tags: braziliandevs, monorepo, systemdesign, microservices
Published: 2023-08-26 20:47:27

### :unlock: Esse artigo é o quarto de uma sequência de estudo em andamento.

[![github](https://img.shields.io/badge/Repositório%20do%20estudo-github-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/jtonynet/cine-ticket-study-microsservices) [![linkedin](https://img.shields.io/badge/Acesse%20o%20meu-linkedin-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/jos%C3%A9-r-99896a39/) [![twitter](https://img.shields.io/badge/Acesse%20o%20meu-twitter-1D9BF0?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/aromademirtilo) [![Instagram](https://img.shields.io/badge/Acesse%20o%20meu-Instagram-E4405F?style=for-the-badge&logo=instagram&logoColor=white)](https://www.instagram.com/learningenuity/) ![Badge Status](https://img.shields.io/badge/STATUS-EM_DESENVOLVIMENTO-green?style=for-the-badge)

---

> **Documento**
> _Um documento é qualquer registro de informações, independentemente do formato ou suporte utilizado para registrá-las._
>
> _Fonte: [Wikipédia](https://pt.wikipedia.org/wiki/Padroniza%C3%A7%C3%A3o)_

O texto abaixo é referente à branch [feature/initial-main-000](https://github.com/jtonynet/cine-ticket-study-microsservices/tree/feature/initial-main-000) do repositório de estudo. Ao final do artigo teremos desenvolvido até o ponto da branch [feature/catalog-service-001](https://github.com/jtonynet/cine-ticket-study-microsservices/tree/feature/catalog-service-001)

---

### :books: 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](https://miro.com/pt/), [Draw.io](https://app.diagrams.net/) 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](https://mermaid.js.org) 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](https://k21.global/br/blog/encerramento-do-sprint#:~:text=A%20Defini%C3%A7%C3%A3o%20de%20Pronto%20%C3%A9,parte%20do%20produto%20em%20desenvolvimento.) pois: 
> _"Documentação é um dos principios de disponibilidade." - [Susan Fowler](https://en.wikipedia.org/wiki/Susan_Fowler)_

<img src="https://media3.giphy.com/media/l0IylOPCNkiqOgMyA/giphy.gif?cid=6c09b952f2txpzao6sq8inl8o4kc3gljp9t15oe3o7zr578p&ep=v1_gifs_search&rid=giphy.gif&ct=g">

O problema da "sala de cinema" já é "velho conhecido" para aqueles que estudaram [UML](https://www.amazon.com.br/UML-2-Uma-Abordagem-Pr%C3%A1tica/dp/8575226460), 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](https://brunonardini.com.br/)_

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shb7xmhiq9cnt0vulve8.png)

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:

<a id="diagrama_er"></a>
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qp2yd8jr68oyomdv2en.png)

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](https://adr.github.io/)

Deixo aqui o contexto com as principais decisões do documento, mas sugiro a você dar uma olhada no [ADR completo no repositório.](https://github.com/jtonynet/cine-ticket-study-microsservices/blob/feature/catalog-service-001/packages/catalog-service/ADR.md)

> "_Estamos projetando um Microsserviço de Catálogo de ingressos de cinema. **Decidimos** usar o framework [Express](https://expressjs.com/) com [TypeScript](https://www.typescriptlang.org/) para criar a API em arquitetura [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). Faremos testes de unidade e integração com [Jest](https://jestjs.io/pt-BR/) e usaremos [Eslint](https://eslint.org/) para linting. Adotaremos principios [SOLID](https://en.wikipedia.org/wiki/SOLID) onde se aplicar. O ORM [Sequelize](https://sequelize.org/) será usado para acessar o banco de dados [PostgreSQL](https://www.postgresql.org/). Utilizaremos [Swagger](https://swagger.io/) para documentação da API. O desenvolvimento acontecerá em um [Monorepo NX](https://nx.dev/), permitindo a organização de múltiplos projetos em um único repositório. [Ceph](https://docs.ceph.com/en/latest/radosgw/s3/) será usado  usado como storage local para os posters, simulando um Amazon S3. Não integraremos autenticação agora, mas consideramos [Keycloak](https://www.keycloak.org/) no futuro. Cache [Redis](https://redis.io/) está sendo considerado para o futuro_"

---
### :construction_worker: 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](https://nx.dev/) 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](https://nodejs.org/en) instalada e do gerenciador de Monorepos `NX`

[![Node.js](https://img.shields.io/badge/Instale%20o%20-Nodejs-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/pt-br/download/package-manager)

Logo após, instalar nosso gerenciador de Monorepo.

[![NX](https://img.shields.io/badge/Instale%20o%20-NX-143055?style=for-the-badge&logo=nx&logoColor=white)](https://nx.dev/getting-started/installation#installing-nx-globally)

Pronto, com isso agora podemos finalmente por a "Mão na Massa" e codar algo que seja aderente as nossas guidelines.

<img src="https://media.tenor.com/r6zafkpW15oAAAAC/show-me-what-you-got-rick-and-morty.gif"/>

Navegue Até a raiz do projeto em seu terminal, para instalar suas dependencias. Digite o seguinte comando:
```bash
$ 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:

```bash
$ nx generate @nrwl/express:app catalog-service
```

Agora sua pasta packages vai se parecer com a estrutura abaixo:
```shell
  $ 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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn1fk2rquyhmuohutu63.png)

Agora rode o seguinte comando para testar seu "microsserviço mínimo":
```bash
$ nx serve catalog-service
```

A saída deve ser similar a:
```bash
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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqidk9dwhkkpb4eku8cf.png)

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.

<br/>

#### :currency_exchange: 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](https://esbuild.github.io/) para essa tarefa. Precisaremos instalar suas dependências e fazer algumas configurações adicionais. Na raiz do projeto, execute o seguinte comando:

```bash
$ 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:

```bash
$ 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:
```json
{
  "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`.
 ```typescript
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.

---

### :game_die: 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

```bash
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á.

```yml
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:

```bash
mkdir packages/catalog-service-db/src/models
```

Sua árvore de diretório deve ficar similar a:
```bash
  $ 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:
```bash
npm install sequelize pg pg-hstore
npm install @types/sequelize --save-dev
```
1. **sequelize**: ORM (Object-Relational Mapping) que você usará para interagir com o banco de dados.
2. **pg**: driver do PostgreSQL para o Node.js, permitindo que o Sequelize se conecte ao PostgreSQL.
3. **pg-hstore**: pacote que lida com o mapeamento de tipos de dados JSON para o PostgreSQL, usado pelo Sequelize.
4. **types/sequelize**: fornece definições de tipos TypeScript para a biblioteca Sequelize

> :writing_hand: **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](https://www.npmjs.com/package/sequelize-typescript), mas como seu mantenedor não é a equipe oficial do Sequelize, não o utilizei.

<br/>

#### :electric_plug: Criando a conexao:

```bash
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: 

```typescript
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;

```

> :writing_hand: **Nota**:
> Atenção especial entre as linhas `3` a `8`. 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.

[![DBeaver](https://img.shields.io/badge/Instale%20o%20-DBeaver-143055?style=for-the-badge&logo=dbeaver&logoColor=white)](https://dbeaver.io/)

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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3iad7k027dlz2t505wc.png) 


Vamos agora criar nossa primeira entidade "filme", estando na raiz do projeto digite os seguintes comandos:
```bash
$ 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](#diagrama_er):

```typescript
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:


```typescript
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:

```typescript
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:

```bash
$ 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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w3094l14naizf2kgxiem.png)

Não deverá ser um fator complicador criar as outras entidades a partir do que já foi exposto aqui e utilizando o [DER](#diagrama_er) como guideline. Sugiro tentar mesmo sem os relacionamentos no momento para praticar e só depois olhar o gabarito em forma de lista abaixo:

<br/>

<details>
  <summary>packages/catalog-service/src/models/room.ts</summary>
  
<pre><code class="language-typescript">
// 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;
};

</pre></code>
</details>

<details>
  <summary>packages/catalog-service/src/models/session.ts</summary>
  
<pre><code class="language-typescript">
// 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) =&gt; {
  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;
};

</pre></code>
</details>

<details>
  <summary>packages/catalog-service/src/models/seat.ts</summary>
  
<pre><code class="language-typescript">
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) =&gt; {
  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;
};

</pre></code>
</details>

<details>
  <summary>packages/catalog-service/src/models/ticket.ts</summary>
  
<pre><code class="language-typescript">
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) =&gt; {
  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;
};

</pre></code>
</details>

<details>
  <summary>packages/catalog-service/src/models/address.ts</summary>
  
<pre><code class="language-typescript">
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) =&gt; {
  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;
};

</pre></code>
</details>

<details>
  <summary>packages/catalog-service/src/models/roomAddress.ts</summary>
  
<pre><code class="language-typescript">
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) =&gt; {
  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;
};

</pre></code>
</details>


<details>
  <summary>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</summary>


```typescript
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;

```
</details>

<br/>

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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17vvmxiydtr650ac1jnh.png)

---

## :nerd_face: 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.

---

## :footprints: 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](https://swagger.io/), respeitando o [REST](https://en.wikipedia.org/wiki/Representational_state_transfer), 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!

---


| :point_left: Anterior | Próximo :point_right: |
|-----------------|----------------|
| [Monorepo & Docker](https://dev.to/learningenuity/03-estudo-de-microsservicos-ingressox-monorepo-e-docker-1l7g) | [Em Breve] ~~REST e e2e~~ |