Coder Social home page Coder Social logo

pedrohvfernandes / learn-to-use-docker Goto Github PK

View Code? Open in Web Editor NEW
1.0 1.0 0.0 1.72 MB

Dockerfile 16.48% TypeScript 77.68% HCL 5.84%
build docker error-handling fakerjs fastify nodejs pagination postgresql swagger swagger-ui tsup typescript zod terraform terraform-project actions

learn-to-use-docker's Introduction

pass.in

O pass.in é uma aplicação de gestão de participantes em eventos presenciais.

A ferramenta permite que o organizador cadastre um evento e abra uma página pública de inscrição.

Os participantes inscritos podem emitir uma credencial para check-in no dia do evento.

O sistema fará um scan da credencial do participante para permitir a entrada no evento.

Requisitos

Requisitos funcionais

  • O organizador deve poder cadastrar um novo evento;
  • O organizador deve poder visualizar dados de um evento;
  • O organizador deve poder visualizar a lista de participantes;
  • O participante deve poder se inscrever em um evento;
  • O participante deve poder visualizar seu crachá de inscrição;
  • O participante deve poder realizar check-in no evento;

Regras de negócio

  • O participante só pode se inscrever em um evento uma única vez;
  • O participante só pode se inscrever em eventos com vagas disponíveis;
  • O participante só pode realizar check-in em um evento uma única vez;

Requisitos não-funcionais

  • O check-in no evento será realizado através de um QRCode;

Documentação da API (Swagger)

Para documentação da API, acesse o link: https://nlw-unite-nodejs.onrender.com/docs

Banco de dados

Nessa aplicação vamos utilizar banco de dados relacional (SQL). Para ambiente de desenvolvimento seguiremos com o SQLite pela facilidade do ambiente.

Diagrama ERD

Diagrama ERD do banco de dados

Estrutura do banco (SQL)

-- CreateTable
CREATE TABLE "events" (
    "id" TEXT NOT NULL PRIMARY KEY,
    "title" TEXT NOT NULL,
    "details" TEXT,
    "slug" TEXT NOT NULL,
    "maximum_attendees" INTEGER
);

-- CreateTable
CREATE TABLE "attendees" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "name" TEXT NOT NULL,
    "email" TEXT NOT NULL,
    "event_id" TEXT NOT NULL,
    "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "attendees_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "events" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "check_ins" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "attendeeId" INTEGER NOT NULL,
    CONSTRAINT "check_ins_attendeeId_fkey" FOREIGN KEY ("attendeeId") REFERENCES "attendees" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "events_slug_key" ON "events"("slug");

-- CreateIndex
CREATE UNIQUE INDEX "attendees_event_id_email_key" ON "attendees"("event_id", "email");

-- CreateIndex
CREATE UNIQUE INDEX "check_ins_attendeeId_key" ON "check_ins"("attendeeId");

Comando para criar o ORM prisma em Sqlite

npx prisma init --datasource-provider SQLite

npx prisma migrate dev 
# ou
npm run db:migrate

npx prisma studio
# ou
npm run db:studio

# Para gerar o prisma client apos a instalar a dependência do prisma
npx prisma generate
# ou
npm run db:generate

Comando do node para ler .env(Variaveis de ambiente)

# Maneira tradicional: Instalar a dependência dotenv
npm install dotenv

# ou

# No package.json, passar --env-file .env na execução do script. O proprio node suporta a leitura do .env. E pelo fato de estarmos usando o tsx ele ja passa o arquivo .env para o node automaticamente: node --env-file .env src/server.js
  "scripts": {
    "dev": "tsx watch --env-file .env src/server.ts"
  }

Anotações:

  • Métodos HTTP para fins semanticos: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD

    • Get - Obter dados
    • Post - Criar dados...
  • Maneiras de passar parametros para o servidor:

    • Query/search params: site.../users?name=Lucas&age=25 --> Parametros passados na URL --> Parametros de busca/filtro --> Esses parametros não são obrigatórios --> Podem ser usados para filtrar os resultados de uma busca

    • Route params: /users/10 (id do usuario) --> site.../users/:id --> site.../users/5 --> Parametros de rota passados na URL --> Identificação de recursos --> Para Put, Delete, Patch ou ate get--> fazer uma operação em um unico registro --> Por exemplo para deletar um usuario --> Esses parametros são obrigatórios

    • Body: { name: 'Lucas', age: 25 } --> Corpo da requisição

    • Headers: { Authorization : 'Bearer 123456' } --> Cabeçalhos --> Contexto da requisição --> Informações mais fixas, para detalhar a requisição/back-end --> Ex: a localização do usuario, o tipo de conteudo que ele aceita, o tipo de conteudo que ele envia, a autenticação do usuario

  • Maneiras de conectar com o banco:

    • Driver nativo: Conexão direta(Baixo nivel) com o banco de dados --> Ex: mysql, pg, sqlite --> Necessita de conhecimento avançado em SQL

    • ORM: Object Relational Mapping --> Mapeamento de objetos para o banco de dados --> Ex: TypeORM, Sequelize, Prisma, Hibernate(Para Java)...

    • Query Builder: Construção de queries SQL com métodos/sintaxe Javascript --> Knex.js...

    • Migrations --> Controle de versão do banco de dados --> Criação de tabelas, alteração de tabelas, exclusão de tabelas...

  • Http status code: Traz o significado da resposta

    • 2xx: Sucesso
    • 3xx: Redirecionamento
    • 4xx: Erro do cliente(Erro em alguma informação enviada por QUEM esta fazendo a chamada p/ API)
    • 5xx: Erro do servidor(Um erro que está acontecendo IDEPENDENTE do que está sendo enviado p/ o servidor)
  • Caso queira gerar regex de maneira facil, basta usar o Chatgpt:

    • Me dê uma função em typescript que gere um slug a partir de um texto sem acentos, símbolos ou espaços, ou seja, pronto para ser usado em uma URL
        export function generateSlug(text: string): string {
        return text
          .normalize('NFD')
          .replace(/[\u0300-\u036f]/g, '')
          .toLowerCase()
          .replace(/[^\w\s-]/g, '')
          .replace(/\s+/g, '-')
      }
  • Arquivo para pre-popular nosso BD, o arquivo seed em prisma

  • Em package.json:

  "prisma": {
    "seed": "tsx prisma/seed.ts"
  }

depois rode o comando para popular o banco:

  npx prisma db seed
  • Documentação da Api usando o Swagger: https://swagger.io/ e iremos usar o fastify junto com ele para gerar o arquivo de documentação yml ou json da API fastify/swagger, o fastify/swagger-ui cria a interface para visualizar/navegar pela documentação da API
  npm i @fastify/swagger @fastify/swagger-ui
  • Para abrir e testar a documentação da API, basta acessar a rota /docs: http://localhost:3000/docs
  • Fazendo o build para subir a aplicação para produção
  npm i tsup -D
  • Script de build para preparar nossa aplicação para produção
  "scripts": {
    # Para gerar o build no formato ESM (EcmaScript Module) de exportação e importação diretamente das funções, classes, variáveis, arquivos...
    "build:esm": "tsup src --format esm",
    # Para gerar o build no formato CommonJS padrão antigo de exportação e importação, module export
    "build:cjs": "tsup src --format cjs",
  }
  • Para rodar o build: npm run start, ou seja, a plicação em JS
  "scripts": {
    "start": "node dist/server.mjs"
  }
  • Devops

    • O docker hub nada mais é que um repositorio de imagens docker, onde podemos subir nossas imagens para serem usadas em qualquer lugar

    • Imagens Docker

    • Imagens Docker node

    • Imagem docker node 20

    • Imagem docker node 20-alpine3.19

    • Para baixar o docker no windows

    • Lembre de ter o WSL2 instalado

    • Como instalar o WSL2 no Windows 10/11 - Linux e Windows Lado a Lado para Iniciantes

    • Após instalar tudo basta iniciar, que qualquer comando via CLI do windows será executado

    • Para baixar a imagem do docker do node da tag 20 na maquina(servidor/pc): docker pull node:20 pode demorar um pouco

      • Retorno após baixar a imagem:
          0: Pulling from library/node
          71215d55680c: Pull complete
          3cb8f9c23302: Pull complete
          5f899db30843: Pull complete
          567db630df8d: Pull complete
          f4ac4e9f5ffb: Pull complete
          eecc94e1c146: Pull complete
          5bbcc6307f26: Pull complete
          18e47472a9da: Pull complete
          Digest: sha256:8a03de2385cb16c4d76aac30bf86ab05f55f1754a5402dc9039916e8043f019a
          Status: Downloaded newer image for node:20
          docker.io/library/node:20
      • Rode um docker image ls para ver as imagens baixadas no seu sistema
        • Retorno do meu:
              REPOSITORY           TAG       IMAGE ID       CREATED        SIZE
              node                 20        8e022d47db62   4 days ago     1.1GB
              bitnami/postgresql   latest    9b84f4e4d643   2 months ago   274MB
              bitnami/redis        latest    45de196aef7e   2 months ago   95.2MB
    • Para baixar a imagem do docker do node da tag 20-alpine3.19: docker pull node:20-alpine3.19 pode demorar um pouco, so que menos, porque ela é 135MB, esse minimo ja da para rodar o node

      • Retorno após baixar a imagem:
            20-alpine3.19: Pulling from library/node
            4abcf2066143: Pull complete
            77152dc4dbd8: Pull complete
            f90945061f81: Pull complete
            c95f6f65e1db: Pull complete
            Digest: sha256:7e227295e96f5b00aa79555ae166f50610940d888fc2e321cf36304cbd17d7d6
            Status: Downloaded newer image for node:20-alpine3.19
            docker.io/library/node:20-alpine3.19
      • Rode um docker image ls para ver as imagens baixadas no seu sistema
        • Retorno do meu:
              REPOSITORY           TAG             IMAGE ID       CREATED        SIZE
              node                 20              8e022d47db62   4 days ago     1.1GB
              node                 20-alpine3.19   bc27e8ab8b21   4 days ago     135MB
              bitnami/postgresql   latest          9b84f4e4d643   2 months ago   274MB
              bitnami/redis        latest          45de196aef7e   2 months ago   95.2MB
    • Para rodar a imagem feita por a gente, basta ir no direitorio onde tem o arquivo Dockerfile e rodar o comando docker build -t nome_da_imagem:v1 . o ponto no final é para dizer que o arquivo Dockerfile está no diretorio atual, os : é para dizer a tag da imagem, o -t é para dar um nome a imagem

      • Exemplo: docker build -t passin:v1 .
      • Exemplo caso você não esteja no terminal aberto com o direitorio da sua aplicação com o dockerfile(vscode basta abrir um terminal de acordo com a pasta) e queira passar o endereço da sua aplicação com o Dockerfile em outro terminal aberto em outro diretorio: docker build -t passin:v1 -f /caminho/para/o/Dockerfile .
      • Resultado:
          docker build -t passin:v1 .
      [+] Building 21.1s (15/21)                                                                                                          docker:default
      => [internal] load build definition from Dockerfile                                                                                          0.1s
      => => transferring dockerfile: 5.12kB                                                                                                        0.0s
      => [internal] load metadata for docker.io/library/node:20                                                                                    0.0s
      => [internal] load metadata for docker.io/library/node:20-alpine3.19                                                                         0.0s
      => [internal] load .dockerignore                                                                                                             0.0s
      => => transferring context: 153B                                                                                                             0.0s
      => [deploy 1/8] FROM docker.io/library/node:20-alpine3.19                                                                                    0.1s
      => [internal] load build context                                                                                                             0.1s
      => => transferring context: 341.52kB                                                                                                         0.0s
      => [base 1/1] FROM docker.io/library/node:20                                                                                                 0.2s
      => [deploy 2/8] WORKDIR /usr/src/app                                                                                                         0.1s
      => [deploy 3/8] RUN npm i -g prisma                                                                                                         10.7s 
      => [dependencies 1/3] WORKDIR /usr/src/app                                                                                                   0.0s 
      => [dependencies 2/3] COPY package.json package-lock.json ./                                                                                 0.0s 
      => [build 2/5] COPY . .                                                                                                                      0.1s 
      => [dependencies 3/3] RUN npm install                                                                                                        16.8s 
      => [build 3/5] COPY --from=dependencies /usr/src/app/node_modules ./node_modules                                                             2.2s
      => [build 4/5] RUN npm run build:esm                                                                                                         1.2s 
      => [build 5/5] RUN npm prune --prod                                                                                                          2.1s 
      => [deploy 4/8] COPY --from=build /usr/src/app/dist ./dist                                                                                   0.0s 
      => [deploy 5/8] COPY --from=build /usr/src/app/node_modules ./node_modules                                                                   1.2s 
      => [deploy 6/8] COPY --from=build /usr/src/app/package.json ./package.json                                                                   0.0s 
      => [deploy 7/8] COPY --from=build /usr/src/app/prisma ./prisma                                                                               0.0s 
      => [deploy 8/8] RUN npx prisma generate                                                                                                      2.1s 
      => exporting to image                                                                                                                        0.9s 
      => => exporting layers                                                                                                                       0.9s 
      => => writing image sha256:5d01caa891d2896bf3e1c6a9818af954bc935ebd5d317163bf93292ffd138f4b                                                  0.0s 
      => => naming to docker.io/library/passin:v1                                                                                                  0.0s 
      
      What's Next?
        View a summary of image vulnerabilities and recommendations → docker scout quickview 
    • Rodando um docker image ls

      • Resultado:
        PS C:\Users\Pedro\OneDrive\Documentos\GitHub\nlw-unite\api> docker image ls
        REPOSITORY           TAG             IMAGE ID       CREATED         SIZE
        passin               v1              5d01caa891d2   5 minutes ago   587MB
        node                 20              8e022d47db62   9 days ago      1.1GB
        node                 20-alpine3.19   bc27e8ab8b21   9 days ago      135MB
        bitnami/postgresql   latest          9b84f4e4d643   2 months ago    274MB
        bitnami/redis        latest          45de196aef7e   2 months ago    95.2MB
        
    • Rodando o docker docker run -p 3001:3000 passin:v1

      • Resultado
        docker run -p 3001:3000 passin:v1
        
        > [email protected] start
        > node dist/server.mjs
        
        Server is running on 
      • definimos a porta do docker com a flag -p, a primeira porta é a porta do docker e a segunda é a porta da nossa aplicação
    • Não havíamos passado as variáveis de ambiente para o docker, logo pode dar algum erro, para solucionar basta passar via arquivo dotenv. Não criamos o dotenv para o docker que seria o ideal, passamos as variaveis de ambiente para o docker diretamente no arquivo dockerfile.

    • Apos isso, de um docker build -t passin:v2 . para atualizar a imagem do docker e depois rode o docker run -p 3001:3000 passin:v2 para rodar a nova imagem

      • Resultado apos colocar as variaveis
        docker run -p 3001:3000 passin:v2
        
        > [email protected] start
        > node dist/server.mjs
        
        Server is running on http://localhost:3000
    • Lembrando que o localhost:3000 é dentro do docker, para nós a porta é 3001 porque usamos o docker para subir a aplicação, usando a porta 3001 para subir a imagem do mesmo, logo se eu quiser acessar agora documentação da minha api não é mais http://localhost:3000/docs e sim http://localhost:3001/docs que foi a porta que definimos ao subir a imagem. Se eu der um ctrl+C a aplicação finaliza, para isso não ocorrer, para não depender desse terminal para rodar a imagem, basta rodar a imagem de outra maneira:

      • docker run -p 3001:3000 -d passin:v2 o -d é para fazer a execução e não travar o meu terminal, dessa forma nossa imagem roda como qualquer outra imagem de terceiros que usamos para rodar em outras aplicações(ex: bd redis, postgress) na nossa maquina usando o docker, ou seja, ficam de fundo rondando, para ver quais imagens estão executando: docker ps, para parar basta ir na UI do Docker e parar ou via CLI docker stop CONTAINER ID
      • Resultado da execução docker run -p 3001:3000 -d passin:v2: 0059444199ecebe25e39a72a7b7209944ac6478a29a1c6ae5b34da95c253516d
      • Resultado da execução pra ver quais imagens estão rodando:
        docker ps
        CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                    NAMES
        0059444199ec   passin:v2   "docker-entrypoint.s…"   57 seconds ago   Up 56 seconds   0.0.0.0:3001->3000/tcp   strange_heyrovsky
        • Aqui vemos que a porta 3001(da nossa imagem) aponta para a porta da nossa aplicação 3000 --> 0.0.0.0:3001->3000/tcp
    • Dockers passados docker ps -a

        CONTAINER ID   IMAGE                       COMMAND                  CREATED          STATUS                      PORTS                    NAMES
        0059444199ec   passin:v2                   "docker-entrypoint.s…"   3 minutes ago    Up 3 minutes                0.0.0.0:3001->3000/tcp   strange_heyrovsky
        148dcfddf162   passin:v2                   "docker-entrypoint.s…"   13 minutes ago   Exited (1) 4 minutes ago                             quizzical_chaum
        bf0ceb014b01   passin:v1                   "docker-entrypoint.s…"   20 minutes ago   Exited (1) 13 minutes ago                            exciting_sammet
        c24c2f06be99   passin:v1                   "docker-entrypoint.s…"   21 minutes ago   Exited (1) 20 minutes ago                            zen_hellman
        4bd857564eb1   bitnami/postgresql:latest   "/opt/bitnami/script…"   3 weeks ago      Exited (0) 3 weeks ago                               url-shortening-system-with-node-postgres-1
        3ef72e5abce5   bitnami/redis:latest        "/opt/bitnami/script…"   3 weeks ago      Exited (0) 3 weeks ago                               url-shortening-system-with-node-redis-1
        240a81c65fc1   bitnami/redis:latest        "/opt/bitnami/script…"   2 months ago     Exited (0) 8 weeks ago                               trilha-node-polls-redis-1
        9c7975954ade   bitnami/postgresql:latest   "/opt/bitnami/script…"   2 months ago     Exited (0) 8 weeks ago                               trilha-node-polls-postgres-1
    • Logs docker logs CONTAINER ID

    • Temos o docker compose, para instalar e rodar imagens de terceiros ou a nossa mesma(dockerfile), nesse caso iremos usar o postgress e a nossa imagem(dockerfile). Para saber se esta instalado basta dar um docker-compose -v

      • Resultado: Docker Compose version v2.24.3-desktop.1
    • O docker compose é uma ferramenta somente para ambiente local, servindo para orquestração de containers. Ou seja, conseguimos ter dois serviços rodando ou mais e fazendo a execução com apenas um comendo na mesma camada de rede, no mesmo container, conseguindo associar volume... Para que consigamos instalar e rodar imagens de terceiros ou a nossa propria imagem(Dockerfile) em um so container(onde cada serviço é um container e cada serviço tem a sua imagem) com um so comando, criamos o docker-compose.yml, onde passamos os serviços que queremos rodar

    • Tiramos as envs do dockerfile e passamos para o docker-compose

    • E agora invés do docker build passando uma nova tag de uma nova versão, rodamos o docker-compose up --build -d para buildar e subir a imagem para criar o container ou docker-compose up -d somente para subir a imagem para criar o container. o -d é mesma logica do docker run, para o console/terminal não ficar travado. Para encerrar via CLI docker-compose down ou via UI. Se quiser subir de novo sem precisar buildar novamente docker-compose up -d

      • Resultado:
            docker-compose up --build -d
            [+] Building 0.0s (0/0)  docker:default
            [+] Building 10.4s (22/22) FINISHED                                                                                                 docker:default
            => [api-pass-in internal] load build definition from Dockerfile                                                                              0.0s
            => => transferring dockerfile: 5.48kB                                                                                                        0.0s 
            => [api-pass-in internal] load metadata for docker.io/library/node:20                                                                        0.0s 
            => [api-pass-in internal] load metadata for docker.io/library/node:20-alpine3.19                                                             0.0s 
            => [api-pass-in internal] load .dockerignore                                                                                                 0.0s 
            => => transferring context: 153B                                                                                                             0.0s 
            => [api-pass-in base 1/1] FROM docker.io/library/node:20                                                                                     0.0s 
            => [api-pass-in internal] load build context                                                                                                 0.0s 
            => => transferring context: 2.98kB                                                                                                           0.0s 
            => [api-pass-in deploy 1/8] FROM docker.io/library/node:20-alpine3.19                                                                        0.0s 
            => CACHED [api-pass-in dependencies 1/3] WORKDIR /usr/src/app                                                                                0.0s
            => CACHED [api-pass-in dependencies 2/3] COPY package.json package-lock.json ./                                                              0.0s 
            => CACHED [api-pass-in dependencies 3/3] RUN npm install                                                                                     0.0s 
            => [api-pass-in build 2/5] COPY . .                                                                                                          0.0s 
            => [api-pass-in build 3/5] COPY --from=dependencies /usr/src/app/node_modules ./node_modules                                                 5.7s 
            => [api-pass-in build 4/5] RUN npm run build:esm                                                                                             1.3s 
            => [api-pass-in build 5/5] RUN npm prune --prod                                                                                              2.2s 
            => CACHED [api-pass-in deploy 2/8] WORKDIR /usr/src/app                                                                                      0.0s 
            => CACHED [api-pass-in deploy 3/8] RUN npm i -g prisma                                                                                       0.0s 
            => CACHED [api-pass-in deploy 4/8] COPY --from=build /usr/src/app/dist ./dist                                                                0.0s 
            => CACHED [api-pass-in deploy 5/8] COPY --from=build /usr/src/app/node_modules ./node_modules                                                0.0s 
            => CACHED [api-pass-in deploy 6/8] COPY --from=build /usr/src/app/package.json ./package.json                                                0.0s 
            => CACHED [api-pass-in deploy 7/8] COPY --from=build /usr/src/app/prisma ./prisma                                                            0.0s 
            => CACHED [api-pass-in deploy 8/8] RUN npx prisma generate                                                                                   0.0s 
            => [api-pass-in] exporting to image                                                                                                          0.0s 
            => => exporting layers                                                                                                                       0.0s 
            => => writing image sha256:7aa827dc2070f3582a67da33d174d84901c01584e54319be76c4937e24dfe583                                                  0.0s 
            => => naming to docker.io/library/api-api-pass-in                                                                                            0.0s 
            [+] Running 0/2
            - Network api_default    Created                                                                                                             0.3s 
            - Container api_pass_in  Starting                                                                                                            0.3s 
            Error response from daemon: driver failed programming external connectivity on endpoint api_pass_in (45318b57010c9a20d82a54cf095761d894decddb0f7d6636289352ed5e4a5ba8): Bind for 0.0.0.0:3001 failed: port is already allocated
      • Ele deu um erro Error response from daemon: driver failed programming external connectivity on endpoint api_pass_in (45318b57010c9a20d82a54cf095761d894decddb0f7d6636289352ed5e4a5ba8): Bind for 0.0.0.0:3001 failed: port is already allocated Porque ja estamos utilizando a porta 3001, basta fazer isso docker ps pegar o container que esta rodando nessa porta e dar um docker stop CONTAINER ID
        • Docker ps:
            CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                    NAMES
            0059444199ec   passin:v2   "docker-entrypoint.s…"   32 minutes ago   Up 32 minutes   0.0.0.0:3001->3000/tcp   strange_heyrovsky
        • docker stop 0059444199ec. Agora podemos rodar o up
          • docker up...
            2024/04/13 15:19:34 http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed
            [+] Building 0.0s (0/0)  docker:default
            [+] Building 0.1s (22/22) FINISHED                                                                                                  docker:default
            => [api-pass-in internal] load build definition from Dockerfile                                                                              0.0s
            => => transferring dockerfile: 5.48kB                                                                                                        0.0s 
            => [api-pass-in internal] load metadata for docker.io/library/node:20-alpine3.19                                                             0.0s 
            => [api-pass-in internal] load metadata for docker.io/library/node:20                                                                        0.0s 
            => [api-pass-in internal] load .dockerignore                                                                                                 0.0s 
            => => transferring context: 153B                                                                                                             0.0s 
            => [api-pass-in base 1/1] FROM docker.io/library/node:20                                                                                     0.0s 
            => [api-pass-in deploy 1/8] FROM docker.io/library/node:20-alpine3.19                                                                        0.0s 
            => [api-pass-in internal] load build context                                                                                                 0.0s 
            => => transferring context: 2.17kB                                                                                                           0.0s 
            => CACHED [api-pass-in deploy 2/8] WORKDIR /usr/src/app                                                                                      0.0s 
            => CACHED [api-pass-in deploy 3/8] RUN npm i -g prisma                                                                                       0.0s 
            => CACHED [api-pass-in dependencies 1/3] WORKDIR /usr/src/app                                                                                0.0s 
            => CACHED [api-pass-in build 2/5] COPY . .                                                                                                   0.0s 
            => CACHED [api-pass-in dependencies 2/3] COPY package.json package-lock.json ./                                                              0.0s 
            => CACHED [api-pass-in dependencies 3/3] RUN npm install                                                                                     0.0s 
            => CACHED [api-pass-in build 3/5] COPY --from=dependencies /usr/src/app/node_modules ./node_modules                                          0.0s 
            => CACHED [api-pass-in build 4/5] RUN npm run build:esm                                                                                      0.0s 
            => CACHED [api-pass-in build 5/5] RUN npm prune --prod                                                                                       0.0s 
            => CACHED [api-pass-in deploy 4/8] COPY --from=build /usr/src/app/dist ./dist                                                                0.0s 
            => CACHED [api-pass-in deploy 5/8] COPY --from=build /usr/src/app/node_modules ./node_modules                                                0.0s 
            => CACHED [api-pass-in deploy 6/8] COPY --from=build /usr/src/app/package.json ./package.json                                                0.0s 
            => CACHED [api-pass-in deploy 7/8] COPY --from=build /usr/src/app/prisma ./prisma                                                            0.0s 
            => CACHED [api-pass-in deploy 8/8] RUN npx prisma generate                                                                                   0.0s 
            => [api-pass-in] exporting to image                                                                                                          0.0s 
            => => exporting layers                                                                                                                       0.0s 
            => => writing image sha256:7aa827dc2070f3582a67da33d174d84901c01584e54319be76c4937e24dfe583                                                  0.0s 
            => => naming to docker.io/library/api-api-pass-in                                                                                            0.0s 
            [+] Running 1/1
            ✔ Container api_pass_in  Started     
    • Agora iremos preparar a aplicação para receber o postgres, removendo o arquivo dev.db que era usado pelo SQlite, depois indo no prisma configuramos para receber o postgres, depois removemos o migration_lock em migrations, depois em cada migration mudamos o autoincrement, porque no postgres não possui autoincrement, usamos o serial que é o equivalente ao autoincrement do SQlite, e o integer tambem não é necessario visto que um campo serial ja é um campo integer no postgres. Mudamos também o tipo DATETIME para TIMESTAMP

    • Agora declaramos outro serviço dentro do docker-compose o postgres

    • Para rodar o docker-compose com o postgres basta rodar o docker-compose up --build -d ele vai fazer baixar, fazer o pull da imagem do postgres(postgres:16.2-alpine3.19) de acordo com a tag que foi definida e o build da nossa imagem(api-api-pass-in) e subir os dois containers(api-pass-in e db-pass-in) em um pacote de container(learn-to-use-docker(nome da nossa aplicação))

          [+] Running 10/10
          ✔ postgres 9 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿]      0B/0B      Pulled                                                                                                      10.9s 
            ✔ 4abcf2066143 Already exists                                                                                                                              0.0s 
            ✔ 128d1b74d24d Pull complete                                                                                                                               0.7s 
            ✔ 0a392327555d Pull complete                                                                                                                               0.6s 
            ✔ 0e14a31643e8 Pull complete                                                                                                                               5.7s 
            ✔ 904e95badb7d Pull complete                                                                                                                               1.2s 
            ✔ 8f6103a2e811 Pull complete                                                                                                                               1.4s 
            ✔ 02b106837f9f Pull complete                                                                                                                               1.8s 
            ✔ d34b010d3edc Pull complete                                                                                                                               2.0s 
            ✔ 696a345da38f Pull complete                                                                                                                               2.5s 
          [+] Building 10.2s (22/22) FINISHED                                                                                                                 docker:default
          => [api-pass-in internal] load build definition from Dockerfile                                                                                              0.0s
          => => transferring dockerfile: 5.48kB                                                                                                                        0.0s 
          => [api-pass-in internal] load metadata for docker.io/library/node:20-alpine3.19                                                                             0.0s 
          => [api-pass-in internal] load metadata for docker.io/library/node:20                                                                                        0.0s 
          => [api-pass-in internal] load .dockerignore                                                                                                                 0.0s 
          => => transferring context: 153B                                                                                                                             0.0s 
          => [api-pass-in base 1/1] FROM docker.io/library/node:20                                                                                                     0.0s 
          => [api-pass-in deploy 1/8] FROM docker.io/library/node:20-alpine3.19                                                                                        0.0s 
          => [api-pass-in internal] load build context                                                                                                                 0.0s 
          => => transferring context: 5.01kB                                                                                                                           0.0s
          => CACHED [api-pass-in dependencies 1/3] WORKDIR /usr/src/app                                                                                                0.0s 
          => CACHED [api-pass-in dependencies 2/3] COPY package.json package-lock.json ./                                                                              0.0s 
          => CACHED [api-pass-in dependencies 3/3] RUN npm install                                                                                                     0.0s 
          => [api-pass-in build 2/5] COPY . .                                                                                                                          0.0s 
          => [api-pass-in build 3/5] COPY --from=dependencies /usr/src/app/node_modules ./node_modules                                                                 2.4s 
          => [api-pass-in build 4/5] RUN npm run build:esm                                                                                                             1.1s 
          => [api-pass-in build 5/5] RUN npm prune --prod                                                                                                              2.1s 
          => CACHED [api-pass-in deploy 2/8] WORKDIR /usr/src/app                                                                                                      0.0s 
          => CACHED [api-pass-in deploy 3/8] RUN npm i -g prisma                                                                                                       0.0s 
          => CACHED [api-pass-in deploy 4/8] COPY --from=build /usr/src/app/dist ./dist                                                                                0.0s 
          => CACHED [api-pass-in deploy 5/8] COPY --from=build /usr/src/app/node_modules ./node_modules                                                                0.0s 
          => CACHED [api-pass-in deploy 6/8] COPY --from=build /usr/src/app/package.json ./package.json                                                                0.0s 
          => [api-pass-in deploy 7/8] COPY --from=build /usr/src/app/prisma ./prisma                                                                                   0.0s 
          => [api-pass-in deploy 8/8] RUN npx prisma generate                                                                                                          3.5s 
          => [api-pass-in] exporting to image                                                                                                                          0.3s 
          => => exporting layers                                                                                                                                       0.3s 
          => => writing image sha256:a480c10407aafab9949edc298586b5db0c6829764f10d5a62b1f3a6d47319570                                                                  0.0s 
          => => naming to docker.io/library/api-api-pass-in                                                                                                            0.0s 
          [+] Running 2/3
          ✔ Container db-pass-in-postgres  Started                                                                                                                     0.9s 
          - Container api_pass_in          Recreated                                                                                                                   1.2s 
          ✔ Container api-pass-in          Started                                                                                                                     0.4s
    • docker-compose up -d para subir a aplicação sem precisar buildar novamente

    • Para ver se os containers estão rodando basta rodar o docker ps e para ver todos os containers que ja rodaram basta rodar o docker ps -a ou container que existem mas que não estão rodando e que estão rodandodocker container ls

    • Para ver o log dos nossos containers via CLI basta rodar o docker logs CONTAINER ID, da para ver os logs via UI

    • Rodamos sempre com build para caso tenha alguma alteração no dockerfile ou no docker-compose quando alterar algo da nossa imagem, ele ja builda a nossa imagem(Dockerfile) e sobe os containers(serviços) com as alterações(api-pass-in e db-pass-in)

    • Para parar algum container especifico basta rodar o docker stop CONTAINER ID

    • Para remover todos os containers basta rodar o docker-compose down

    • Para remover algum container(ex: db-pass-in) especifico do nosso pacote de container(ex: learn-to-use-docker) basta rodar o docker rm CONTAINER ID lembrando que a imagem ainda vai estar la(ex:postgres:16.2-alpine3.19) no docker, ou seja, no sistema, para remover a imagem basta rodar o docker rmi IMAGE ID para ver os id das imagens basta rodar o docker image ls

    • para subir somente o container que foi interrompido basta rodar o docker-compose up -d NOME_DO_SERVIÇO --> docker-compose up -d postgres ou docker-compose up Sobe tudo que estiver no docker-compose.yml

    • Caso seja necessario fazer migrations no banco de dados da nossa api ao subir os containers, basta ir no comando start no package.json e adicionar o comando de migration no start npm run db:migrate-deploy && node dist/server.mjs e no Dockerfile deixar no CMD npm start. Não tem problema de deixar o comando da migration no start, porque se paramos algum container que não seja da api(api-pass-in) ao dar o docker-compose up -d ele vai subir somente o container que foi interrompido, mas caso interrompa o container da api e execute o npm run start ao subir o container da api e rodar as migration não sera um problema, porque o volumes do postgres não são apagados, logo as migrations não serão executadas novamente

    • Então lembre de criar o volumes para o container do banco de dados, para que os dados não se percam quando o container do postgres for removido e quando o container da api desligar, subir e rodar a migrations novamente ao subir o container da api, para isso basta ir no docker-compose.yml e adicionar o volumes no container do Banco de dados.

    • Depois de um docker-compose up --build -d para subir a aplicação e pegar o novo package.json com o comando start alterado.

    • Iniciar:

      • npm i
      • npx prisma generate
    • Como iniciar a aplicação via Dev e bd docker localmente:

      • Sobe a nossa api normalmente npm run dev
      • Sobe o serviço do Bd via docker docker-compose up -d postgres
      • para testar na web: localhost:3000/docs
    • Caso de erro nas migrations ou caso não consiga inserir algum evento pela docs ou caso você mude a env de conexão com o banco pelo .env, basta dar um npm run db:migrate ou npm run db:migrate-deploy para rodar as migrations novamente Apos rodar ira aparecer isso no terminal do vscode:

      > [email protected] db:migrate-deploy
      > prisma migrate deploy
    
      Environment variables loaded from .env
      Prisma schema loaded from prisma\schema.prisma
      Datasource "db": PostgreSQL database "attendee-postgres", schema "public" at "attendee-postgres-do-user-16367090-0.c.db.ondigitalocean.com:25060"
    
      5 migrations found in prisma/migrations
    
      Applying migration `20240404175946_create_table_events`
      Applying migration `20240405204149_create_table_attendees`
      Applying migration `20240405213357_add_uniqueness_on_event_id_and_email`
      Applying migration `20240406174022_create_check_ins_table`
      Applying migration `20240406190256_add_cascades`

    Talvez de esse erro, mas não tem problema porque ele ja aplicou as migrations. Mas caso não queria ver esse erro, basta deletar todas as migrations e rodar o npm run db:migrate-deploy ou npm run db:migrate novamente. Isso pode estar ocorrendo porque a aplicação no prisma estava com SQlite e agora esta com o postgres

        Error: P3018
    
        A migration failed to apply. New migrations cannot be applied before the error is recovered from. Read more about how to resolve migration issues in a production database: https://pris.ly/d/migrate-resolve
    
        Migration name: 20240406190256_add_cascades
    
        Database error code: 42601
    
        Database error:
        ERROR: syntax error at or near "PRAGMA"
    
        Position:
          0
          1 -- RedefineTables
          2 PRAGMA foreign_keys=OFF;
    
        DbError { severity: "ERROR", parsed_severity: Some(Error), code: SqlState(E42601), message: "syntax error at or near \"PRAGMA\"", detail: None, hint: None, position: Some(Original(19)), where_: None, schema: None, table: None, column: None, datatype: None, constraint: None, file: Some("scan.l"), line: Some(1241), routine: Some("scanner_yyerror") }
    • Como iniciar a aplicação toda via Docker localmente:

      • Builda a nossa imagem e ja sobe todos os serviços(containers)docker-compose up --build -d
      • Caso ja tenha buildado a nossa imagem(Dockerfile) da api: docker-compose up -d
      • para testar na web: localhost:3001/docs
    • Caso de error de migration no docker ou caso não consiga inserir algum evento pela docs( "message": "Internal server error!") ou troque a env do BD no docker-compose basta, coloque no script de start do package.json o comando de migration npm run db:migrate-deploy && node dist/server.mjs e no Dockerfile deixar o CMD npm start, para que ao subir o container da api ele execute as migrations e depois inicie a aplicação.

    • E depois buildar a imagem da api novamente e ja subir os serviços(containers): docker-compose up --build -d

    • Caso esteja dando erro no log do container da api com esse comando no start, execute o apenas uma vez buildando a aplicação e em seguida no start deixe somente o node dist/server.mjs

    • e rode novamente o build da imagem

    • O que podera acontecer ao rodar o build com o comando npm run db:migrate-deploy && node dist/server.mjs

      • Apos rodar ira aparecer isso no log do docker:

          > [email protected] db:migrate-deploy
          > prisma migrate deploy
        
          Environment variables loaded from .env
          Prisma schema loaded from prisma\schema.prisma
          Datasource "db": PostgreSQL database "attendee-postgres", schema "public" at "attendee-postgres-do-user-16367090-0.c.db.ondigitalocean.com:25060"
        
          5 migrations found in prisma/migrations
        
          Applying migration `20240404175946_create_table_events`
          Applying migration `20240405204149_create_table_attendees`
          Applying migration `20240405213357_add_uniqueness_on_event_id_and_email`
          Applying migration `20240406174022_create_check_ins_table`
          Applying migration `20240406190256_add_cascades`

        Talvez de esse erro no log do docker do container da api(api-pass-in), mas não tem problema porque ele ja aplicou as migrations. Mas caso não queria ver esse erro, basta deletar todas as migrations e rodar o npm run db:migrate-deploy ou npm run db:migrate novamente localmente, e depois buildar a imagem da api novamente e ja subir os serviços(containers): docker-compose up --build -d e lembre de deixar o comando no start do package.json npm run db:migrate-deploy && node dist/server.mjs. Isso pode estar ocorrendo porque a aplicação no prisma estava com SQlite e agora esta com o postgres

            Error: P3018
        
            A migration failed to apply. New migrations cannot be applied before the error is recovered from. Read more about how to resolve migration issues in a production database: https://pris.ly/d/migrate-resolve
        
            Migration name: 20240406190256_add_cascades
        
            Database error code: 42601
        
            Database error:
            ERROR: syntax error at or near "PRAGMA"
        
            Position:
              0
              1 -- RedefineTables
              2 PRAGMA foreign_keys=OFF;
        
            DbError { severity: "ERROR", parsed_severity: Some(Error), code: SqlState(E42601), message: "syntax error at or near \"PRAGMA\"", detail: None, hint: None, position: Some(Original(19)), where_: None, schema: None, table: None, column: None, datatype: None, constraint: None, file: Some("scan.l"), line: Some(1241), routine: Some("scanner_yyerror") }
    • Pipeline e actions

    • Serviço apartado de banco de dados

      Não iremos criar o bd diretamente na digitalocean, vamos criar um bd pelo terraform, porque no digitalocen seria algo bem simples, mas ao longo prazo teríamos problemas, por isso iremos criar usando os conceitos de infraestrutura como código(IaC) com o terraform

      • Terraform

      • Terraform register

      • Browser Providers

      • Existe varias, como AWS, mas iremos usar a digitalocean

        • DigitalOcean
          • Use Provider

            terraform {
              required_providers {
                digitalocean = {
                  source = "digitalocean/digitalocean"
                  version = "2.36.0"
                }
              }
            }
            
            provider "digitalocean" {
              # Configuration options
              # Passagem de um token associado a nossa conta
              
            }
          • Documentation

          • digitalocean_database_cluster Todas as configurações que iriamos passar peo console, passamos agora por aqui: Para a gente criar um cluster: Primeiro configuramos o provider, depois dentro do provedor eu falo que eu quero utilizar o recurso digitalocean_database_cluster, e dentro desse recurso eu passo as configurações(que eu ia passar pelo console da digitalonce) que eu quero para o meu cluster. Esse cluster é para o BD postgres

          • Exemplo:

             resource "digitalocean_database_cluster" "postgres-example" {
               name       = "example-postgres-cluster"
               engine     = "pg"
               version    = "15"
               size       = "db-s-1vcpu-1gb"
               region     = "nyc1"
               node_count = 1
             }
          • Esse é para criar um banco e um cluster para um banco: digitalocean_database_db
                # cria um banco em segundo, usando o id do cluster criado
                resource "digitalocean_database_db" "database-example" {
                  cluster_id = digitalocean_database_cluster.postgres-example.id
                  name       = "foobar"
                }
                # Cria um cluster primeiro
                resource "digitalocean_database_cluster" "postgres-example" {
                  name       = "example-postgres-cluster"
                  engine     = "pg"
                  version    = "11"
                  size       = "db-s-1vcpu-1gb"
                  region     = "nyc1"
                  node_count = 1
                }
      • Criamos uma pasta para a api e uma para terraform, ou deixa a api na raiz do repositorio e criamos uma pasta para o terraform

      • Baixe as extensoes do terraform no vscode: HashiCorp Terraform, Terraform e Terraform Autocomplete

      • Lembre de instalar o terraform na maquina: Terraform

        • Como instalar(Lembre de fechar e abrir o terminal para que o terraform seja reconhecido, se não der abre e fechar o vscode)
      • Agora iremos iniciar o terraform terraform init, lembre de abrir o terminal na pasta do terraform com os arquivos do Terraform, apos isso ele ira gerar .terraform.lock.hcl. O terraform init ira iniciar o nosso plugin, procurar ele e instalar

      • Depois terraform fmt para ver se os arquivos estão formatados corretamente

      • Depois terraform plan, ele ira criar a pasta .terraform, onde ele cria um plano de execução, ele planifica.

          Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
            + create
        
          Terraform will perform the following actions:
        
            # digitalocean_database_cluster.db-cluster will be created
            + resource "digitalocean_database_cluster" "db-cluster" {
                + database             = (known after apply)
                + engine               = "pg"
                + host                 = (known after apply)
                + id                   = (known after apply)
                + name                 = "attendee-postgres"
                + node_count           = 1
                + password             = (sensitive value)
                + port                 = (known after apply)
                + private_host         = (known after apply)
                + private_network_uuid = (known after apply)
                + private_uri          = (sensitive value)
                + project_id           = (known after apply)
                + region               = "nyc1"
                + size                 = "db-s-1vcpu-1gb"
                + storage_size_mib     = (known after apply)
                + uri                  = (sensitive value)
                + urn                  = (known after apply)
                + user                 = (known after apply)
                + version              = "16"
              }
        
            # digitalocean_database_db.db-name will be created
            + resource "digitalocean_database_db" "db-name" {
                + cluster_id = (known after apply)
                + id         = (known after apply)
                + name       = "attendee-postgres"
              }
        
          Plan: 2 to add, 0 to change, 0 to destroy.
        
        • O plano dele inidica ele vai criar o cluster, depois o BD e no plano dele ele vai adicionar 2 recursos. O terraform é baseado em estado, o famoso tf state, o estado do cluster, é legal que esse arquivo fique em um lugar remoto e não na nossa maquina
        • Depois terraform apply -auto-approve, ele ira aplicar o plano de execução, o -auto-approve é para não travar o console
        • Apos isso, ele ira criar o arquivo terraform.tfstate e ira criar o nosso cluster
        • Se você ir na digital once em database, você vera o cluster sendo criado e depois o banco. Apos a criação o tfstate podera ser aberto. E apos a criação teremos um banco de dados remoto. Na parte o overview na digital ocean é possivel ver a senha do banco. digitalocean_database_db.db-name --> banco, digitalocean_database_cluster.db-cluster -> cluster
          digitalocean_database_cluster.db-cluster: Creating...
          digitalocean_database_cluster.db-cluster: Still creating... [10s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [20s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [30s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [40s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [50s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m0s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m10s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m20s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m30s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m40s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [1m50s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m0s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m10s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m20s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m30s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m40s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [2m50s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [3m0s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [3m10s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [3m20s elapsed]
          digitalocean_database_cluster.db-cluster: Still creating... [3m30s elapsed]
          digitalocean_database_cluster.db-cluster: Creation complete after 3m36s [id=f1a889ad-09e5-48dd-b0f8-d7d89239dcb0]
          digitalocean_database_db.db-name: Creating...
          digitalocean_database_db.db-name: Creation complete after 2s [id=f1a889ad-09e5-48dd-b0f8-d7d89239dcb0/database/attendee-postgres]
        
          Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
        • Agora quando for alterar algo, mudar o plano, colocar mais maquinas, sempre via IaC terraform, nunca manualmente pelo console
        • Se eu quiser deletar terraform plan -destroy ou comentar ou tirar o recurso do main.tf e rodar o terraform apply -auto-approve
        • O terraform se orienta pelo arquivo gerado apos o apply terraform.tfstate, logo, se deletarmos ele e rodar um plan, ele vai entender que precisamos criar esses recursos novamente, isso vai gerar uma anomalia, dando problema para criar o mesmo recurso com o mesmo nome...
        • Agora iremos conectar a nossa aplicação com o banco
          • Na Digital once Em overview>Connection parameters>Connection strin>database/pool:defaultdb>attendee-postgres> copiamos a string de conexão: postgresql://doadmin:SHOW_PASSWORD@attendee-postgres-do-user-16367090-0.c.db.ondigitalocean.com:25060/attendee-postgres?sslmode=require
          • Podemos testar colocando no env da nossa aplicação em DATABASE_URL, lembre de rodar as migrations apos mudar o env do banco, seja no .env quanto no docker compose(Pode dar os erros que foram listados antes de entrar na parte do terraform ao fazer as migrations tanto com docker mudando o script de start, quanto fora do docker fazendo manual usando o comando de migração do prisma).
        • Ao tentar enviar os arquivos terraform.tfstate, .terraform.lock.hcl e a pasta .terraform para o github, ele não vai deixar, porque são arquivos sensíveis, logo, devemos criar um arquivo .gitignore e colocar esses arquivos la, para que não sejam enviados para o github

learn-to-use-docker's People

Contributors

pedrohvfernandes avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.