diff --git a/material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png b/material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png new file mode 100644 index 0000000..fc64d8d Binary files /dev/null and b/material-sistemas-distribuidos/src/assets/img/49_vsc_using_mysqlconnector.png differ diff --git a/material-sistemas-distribuidos/src/css/code.css b/material-sistemas-distribuidos/src/css/code.css index be8c532..e359930 100644 --- a/material-sistemas-distribuidos/src/css/code.css +++ b/material-sistemas-distribuidos/src/css/code.css @@ -29,4 +29,5 @@ pre:has(code) { border-radius: 10px; margin-top: var(--paragraph-spacing); margin-bottom: var(--paragraph-spacing); + overflow-x: auto; } \ No newline at end of file diff --git a/material-sistemas-distribuidos/src/routes/+page.svelte b/material-sistemas-distribuidos/src/routes/+page.svelte index f582aba..a4a8812 100644 --- a/material-sistemas-distribuidos/src/routes/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/+page.svelte @@ -33,11 +33,7 @@
  • Estrutura de arquivos
  • Criando e executando endpoints
  • Definindo os endpoints da Biblioteca
  • -
  • Conectando com o banco de dados
  • -
  • Mantendo livros
  • -
  • Emprestando livros
  • -
  • Sobre o protocolo HTTP
  • -
  • A arquitetura REST
  • +
  • Conectando com o banco de dados
  • diff --git a/material-sistemas-distribuidos/src/routes/bd/+page.svelte b/material-sistemas-distribuidos/src/routes/bd/+page.svelte index 8e6dd05..1364d88 100644 --- a/material-sistemas-distribuidos/src/routes/bd/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/bd/+page.svelte @@ -1,3 +1,7 @@ + + Conectando com um banco de dados @@ -10,42 +14,769 @@

    - Os endpoints que escrevemos no capítulo anterior não fazem nada além de exibir os dados que você envia na requisição. - Para que possamos fazer com que eles persistam os dados enviados, vamos utilizar um banco de dados. - Neste capítulo, vamos criar a comunicação entre o nosso projeto e esse banco de dados. + Os endpoints que escrevemos no capítulo anterior não fazem nada além de devolver os dados exatamente como você os envia na requisição. + Para que possamos fazer com que estes dados sejam persistidos (ou seja, registrados em algum lugar onde possamos consultá-los futuramente), vamos utilizar um banco de dados. + Neste capítulo, vamos criar a comunicação entre o nosso projeto e um gerenciador de banco de dados MariaDB, + um fork open-source do MySQL criado e mantido pelos seus desenvolvedores originais.

    - Vamos utilizar um banco de dados MySQL, acessível através das credenciais: + Vamos utilizar um banco de dados previamente criado para persistir e consultar livros. + Para acessá-lo a partir do nosso projeto, precisamos de uma biblioteca. Como o MariaDB é um fork do + MySQL, os dois são normalmente intercambiáveis. Bibliotecas desenvolvidas para o MySQL quase sempre irão funcionar com o MariaDB, com exceção de algumas + funcionalidades específicas. A documentação do MariaDB recomenda a utilização da + biblioteca MySqlConnector, portanto, iremos instalá-la no nosso projeto.

    - -

    MySqlConnector

    - Para gerenciar conexões e realizar consultas, vamos utilizar a biblioteca MySqlConnector. Abra - o terminal no seu projeto, e execute o comando: + Abra o terminal no seu projeto, e execute o comando:

    {`cd Biblioteca
     dotnet add package MySqlConnector`}

    - Note que o comando precisa ser executado no diretório onde existe o arquivo Biblioteca.csproj, - por isso o comando cd Biblioteca. + Note que o comando precisa ser executado no diretório onde existe o arquivo Biblioteca.csproj. Para confirmar se a biblioteca foi adicionada corretamente, + adicione a seguinte linha no topo do seu Program.cs. +

    + +
    {`using MySqlConnector;`}
    + +

    + Se o Visual Studio Code não mostra nenhum erro, então a instalação foi bem sucedida. +

    + +
    + +
    + +

    Modelo de dados

    + +

    + O banco de dados que vamos utilizar possui uma tabela com o nome Livro. Esta tabela possui os seguintes campos: +

    + +
    {`Isbn         VARCHAR(255) PRIMARY KEY NOT NULL,
    +Titulo       VARCHAR(512) NOT NULL,
    +Autor        TEXT         NOT NULL,
    +Genero       TEXT         NOT NULL,
    +Descricao    TEXT         NOT NULL,
    +Foto         TEXT         NOT NULL,
    +Keywords     TEXT         NOT NULL,
    +Ativo        BOOLEAN      NOT NULL DEFAULT 0,
    +CriadoEm     DATETIME     NOT NULL,
    +AtualizadoEm DATETIME     NOT NULL,`}
    + +

    Classe de modelo e repositório

    + +

    + Vamos criar duas novas classes no projeto: uma classe Livro que irá espelhar a tabela no banco de dados, e uma classe LivroRepository que irá possuir + métodos para a consulta e manipulação dos registros nessa tabela. Mas antes disso, vamos limpar o Program.cs, removendo partes do código que escrevemos anteriormente apenas para testar, e que + não vamos precisar mais, deixando-o pronto para utilizarmos as novas classes que iremos criar. +

    + +

    + Apague todo o conteúdo do seu Program.cs e deixe-o exatamente assim: +

    + +
    {`var builder = WebApplication.CreateBuilder(args);
    +
    +var app = builder.Build();
    +
    +// Obtém uma lista com os livros registrados.
    +app.MapGet("/livros", () =>
    +{
    +    
    +});
    +
    +// Cria um novo livro.
    +app.MapPost("/livros", () =>
    +{
    +    
    +});
    +
    +// Edita um livro.
    +app.MapPut("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +// Obtém os dados de um livro individual.
    +app.MapGet("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +// Remove um livro.
    +app.MapDelete("/livros/{isbn}", () =>
    +{
    +    
    +});
    +
    +app.Run();`}
    + +

    + Tendo feito isso, crie uma pasta Biblioteca/Models e, dentro dela, crie um arquivo Livro.cs. + Dentro deste arquivo, vamos criar uma classe Livro com campos que refletem os campos na tabela do banco de dados. +

    + +
    {`namespace Biblioteca.Models;
    +
    +public class Livro
    +{
    +    public string? Isbn { get; set; }
    +    public string? Titulo { get; set; }
    +    public string? Autor { get; set; }
    +    public string? Genero { get; set; }
    +    public string? Descricao { get; set; }
    +    public string? Foto { get; set; }
    +    public string? Keywords { get; set; }
    +    public bool Ativo { get; set; }
    +    public DateTime CriadoEm { get; set; }
    +    public DateTime AtualizadoEm { get; set; }
    +}`}
    + +

    + Agora, crie uma pasta Biblioteca/Repositories e, dentro dela, crie um arquivo LivroRepository.cs, + onde existirá a classe LivroRepository. +

    + +
    {`namespace Biblioteca.Repositories;
    +
    +public class LivroRepository
    +{
    +
    +}`}
    + +

    + Nesta classe, vamos precisar de uma constante que vai conter uma string de conexão, que servirá para indicar para o MySqlConnector o endereço e as credenciais do banco de dados que vamos utilizar. +

    + +
    {`namespace Biblioteca.Repositories;
    +
    +public class LivroRepository
    +{
    +    private const string ConnString = "Server=gbrl.dev;Port=5306;User ID=sistemasdistribuidos.aluno;Password=eW03avS7M8kOUL1A9bZWW2RTIfzEI1Di;Database=sistemasdistribuidos";
    +}`}
    + +

    Listagem de livros

    + +

    + Vamos escrever nesta classe métodos para as operações de listagem, consulta, inclusão, edição e remoção de livros na base de dados. Vamos começar pela + operação de listagem. +

    + +
    {`public async Task> Obter(int pagina)
    +{
    +
    +}`}
    + +

    + Este método é público (public), assíncrono (async Task), retorna um IEnumerable<Livro>, e recebe + como parâmetro um valor inteiro que representa a página da consulta +

    + +

    + Um método público é um método que pode ser executado por qualquer outro método no projeto. O nosso método precisa ser público pois ele vai ser chamado + nos endpoints que estão no Program.cs. +

    + +

    + O valor de retorno será um IEnumerable<Livro>. Um IEnumerable é uma interface do dotnet que representa + qualquer sequência iterável de objetos. Por exemplo, um objeto List<T> ou um vetor T[] são iteráveis, + portanto podem ser retornados neste método. +

    + +

    + Um método assíncrono é um método que pode ser executado assíncronamente, de forma independente do fluxo de execução do método que o chamou. + Nós não vamos utilizar nenhuma forma de paralelismo, mas precisamos que nosso método seja assíncrono mesmo assim. +

    + +

    + O parâmetro int pagina recebido indica a página da consulta. Você provavelmente já usou algum site onde interagiu com uma listagem de um conteúdo que era feita + através de páginas, a ideia aqui é a mesma. Imagine que temos três milhões de livros na base de dados, nós não queremos que nosso método leia todos eles. + Ao invés disso, vamos retornar os livros de forma paginada, com N livros por página. +

    + +

    + Agora, vamos implementar o método, que ficará assim: +

    + +
    {`public async Task> Obter(int pagina)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var take   = 30;
    +    var offset = take * Math.Max(pagina-1, 0);
    +    var lista  = new List();
    +
    +    cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro ORDER BY CriadoEm LIMIT @offset,@take";
    +    cmd.Parameters.AddWithValue("offset", offset);
    +    cmd.Parameters.AddWithValue("take", take);
    +
    +    using var reader = await cmd.ExecuteReaderAsync();
    +
    +    while (await reader.ReadAsync())
    +    {
    +        lista.Add(new()
    +        {
    +            Isbn         = reader.GetString(0),
    +            Titulo       = reader.GetString(1),
    +            Autor        = reader.GetString(2),
    +            Genero       = reader.GetString(3),
    +            Descricao    = reader.GetString(4),
    +            Foto         = reader.GetString(5),
    +            Keywords     = reader.GetString(6),
    +            Ativo        = reader.GetBoolean(7),
    +            CriadoEm     = reader.GetDateTime(8),
    +            AtualizadoEm = reader.GetDateTime(9),
    +        });
    +    }
    +
    +    return lista;
    +}`}
    + +

    + Vamos analisar cada trecho do método. +

    + +
    {`using var conn = new MySqlConnection(ConnString);
    +using var cmd  = conn.CreateCommand();
    +
    +await conn.OpenAsync();`}
    + +

    + As variáveis conn e cmd representam, respectivamente, a conexão com o banco de dados, e o comando que iremos executar. + A declaração de ambas é feita com using pois estes dois objetos alocam recursos que devem ser liberados ao fim da execução do método. O + using é uma construção da linguagem que garante que isso aconteça sempre. +

    + +

    + Com await conn.OpenAsync() fazemos com que a conexão com o banco de dados seja aberta. +

    + +

    + Note que conn.OpenAsync() também é um método assíncrono (pois ele retorna Task). Por isso, devemos prefixar a chamada deste método com + um await. Isto é uma indicação de que queremos aguardar a conclusão de conn.OpenAsync() antes de prosseguirmos com a execução do nosso método. + E para utilizarmos um await, o nosso método deve ser também, obrigatoriamente, assíncrono. Por isso o declaramos como async Task. +

    + +

    + O modelo de programação assíncrona é uma característica que + é praticamente ubíqua no dotnet, mas não precisamos nos aprofundar nela por enquanto. Basta saber que devemos utilizar await em métodos assíncronos, e + que se o utilizarmos, o nosso método deve ser também assíncrono, sendo declarado com async Task. +

    + +
    {`var take   = 30;
    +var offset = take * Math.Max(pagina-1, 0);
    +var lista  = new List();`}
    + +

    + Em seguida criamos três variáveis. take é o número de livros que queremos obter por página. + offset é um número que indica quantos registros pular para obter a página desejada. Por exemplo, se + desejamos a primeira página, devemos obter os n primeiros livros. Já se quisermos a segunda página, + vamos pular n livros e depois obter os n próximos. + Generalizando, para obter a página p (considerando que as páginas começam do 1), com n elementos, precisamos pular n(p-1) livros. +

    + +

    + Como int pagina é um valor inteiro que pode assumir valores negativos, fazemos Math.Max(pagina-1, 0) como uma garantia para que offset nunca tenha um valor menor do que zero. +

    + +

    + A variável lista é uma lista de objetos Livro que irá guardar todos os resultados da consulta, e depois será retornada. +

    + +
    {`cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro ORDER BY CriadoEm LIMIT @offset,@take";
    +cmd.Parameters.AddWithValue("offset", offset);
    +cmd.Parameters.AddWithValue("take", take);`}
    + +

    + A primeira linha adiciona a consulta que queremos executar à variável cmd. As duas linhas seguintes adicionam take e offset + como parâmetros da consulta. +

    + +
    {`using var reader = await cmd.ExecuteReaderAsync();`}
    + +

    + Enfim, executamos a consulta através de cmd.ExecuteReaderAsync(). Este método é assíncrono, portanto deve ser prefixado por await, e além disso também aloca + recursos que devem ser liberados com using. O retorno desta função é um objeto onde podemos ler os resultados da consulta. +

    + +
    {`while (await reader.ReadAsync())
    +{
    +    lista.Add(new()
    +    {
    +        Isbn         = reader.GetString(0),
    +        Titulo       = reader.GetString(1),
    +        Autor        = reader.GetString(2),
    +        Genero       = reader.GetString(3),
    +        Descricao    = reader.GetString(4),
    +        Foto         = reader.GetString(5),
    +        Keywords     = reader.GetString(6),
    +        Ativo        = reader.GetBoolean(7),
    +        CriadoEm     = reader.GetDateTime(8),
    +        AtualizadoEm = reader.GetDateTime(9),
    +    });
    +}`}
    + +

    + Este laço é repetido enquanto await reader.ReadAsync() retornar true, o que significa que existe um resultado da consulta que pode ser lido. + Quando isso acontece, podemos ler as colunas do resultado através dos métodos de reader, como reader.GetString(0). O número passado como + parâmetro para estas funções indica a posição da coluna lida. Por exemplo, na consulta que realizamos (SELECT Isbn, Titulo, ...), a coluna Isbn é a primeira, + portanto, para ler este valor, devemos executar reader.GetString(0). Titulo é a segunda, então o seu valor é obtido através de reader.GetString(1), e assim + sucessivamente. +

    + +

    + Realizamos a leitura de todas as colunas do resultado, e montamos um objeto do tipo Livro, o qual adicionamos à lista lista, que é o valor de retorno do método de listagem. +

    + +
    {`return lista;`}
    + +

    + Para testarmos este método, vamos chamá-lo no endpoint de listagem em Program.cs +

    + +
    {`// Obtém uma lista com os livros registrados.
    +app.MapGet("/livros", async () =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Obter(pagina: 1);
    +
    +    return res;
    +});`}
    + +

    + Como LivroRepository.Obter(int) é assíncrono, o endpoint também deve ser. Fazemos isso adicionando async na declaração da sua função. +

    + +
    {`app.MapGet("/livros", async () => ...`}
    + +

    + Agora execute este endpoint. Se tudo deu certo, você verá alguns livros cadastrados no retorno. +

    + +

    Obtendo um livro pelo ISBN

    + +

    + O ISBN é a chave primária da tabela. Vamos implementar um método que consulta um único livro a partir deste dado. +

    + +
    {`public async Task Obter(string isbn)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Isbn=@isbn";
    +    cmd.Parameters.AddWithValue("isbn", isbn);
    +
    +    using var reader = await cmd.ExecuteReaderAsync();
    +
    +    var existe = await reader.ReadAsync();
    +
    +    if (!existe)
    +    {
    +        throw new Exception($"Livro com ISBN {isbn} não encontrado");
    +    }
    +
    +    return new()
    +    {
    +        Isbn         = reader.GetString(0),
    +        Titulo       = reader.GetString(1),
    +        Autor        = reader.GetString(2),
    +        Genero       = reader.GetString(3),
    +        Descricao    = reader.GetString(4),
    +        Foto         = reader.GetString(5),
    +        Keywords     = reader.GetString(6),
    +        Ativo        = reader.GetBoolean(7),
    +        CriadoEm     = reader.GetDateTime(8),
    +        AtualizadoEm = reader.GetDateTime(9),
    +    };
    +}`}
    + +

    + As três primeiras linhas deste método, assim como no de listagem, abre uma conexão com o banco de dados. +

    + +
    {`cmd.CommandText = "SELECT Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm FROM Livro WHERE Isbn=@isbn";
    +cmd.Parameters.AddWithValue("isbn", isbn);`}
    + +

    + A consulta é montada na variável cmd, o parâmetro isbn é adicionado ao comando. +

    + +
    {`using var reader = await cmd.ExecuteReaderAsync();
    +
    +var existe = await reader.ReadAsync();
    +
    +if (!existe)
    +{
    +    throw new Exception($"Livro com ISBN {isbn} não encontrado");
    +}
    +
    +return new()
    +{
    +    Isbn         = reader.GetString(0),
    +    Titulo       = reader.GetString(1),
    +    Autor        = reader.GetString(2),
    +    Genero       = reader.GetString(3),
    +    Descricao    = reader.GetString(4),
    +    Foto         = reader.GetString(5),
    +    Keywords     = reader.GetString(6),
    +    Ativo        = reader.GetBoolean(7),
    +    CriadoEm     = reader.GetDateTime(8),
    +    AtualizadoEm = reader.GetDateTime(9),
    +};`}
    + +

    + Assim como na listagem, await cmd.ExecuteReaderAsync() é chamado para ler os resultados da consulta. + Como esperamos que só haja um registro com aquele ISBN, executamos await reader.ReadAsync() apenas uma vez. + Se este método retorna false, é por que nenhum registro foi encontrado com aquele ISBN. Neste caso, lançamos um erro. + Do contrário, utilizamos os métodos de leitura do registro lido para montar um objeto Livro que será retornado. +

    + +

    + Vamos chamar este novo método ao endpoint de consulta no Program.cs. +

    + +
    {`// Obtém os dados de um livro individual.
    +app.MapGet("/livros/{isbn}", async (string isbn) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Obter(isbn);
    +
    +    return res;
    +});`}
    + +

    Criando livros

    + +

    + Vamos implementar agora o método de criação de livros: +

    + +
    {`public async Task Criar(Livro dados)
    +{
    +
    +}`}
    + +

    + Este método recebe um objeto Livro como parâmetro, contendo os dados do novo livro que deve ser inserido no banco de dados. Vamos implementá-lo. +

    + +
    {`public async Task Criar(Livro dados)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var livro = new Livro
    +    {
    +        Isbn         = dados.Isbn?.Trim() ?? "",
    +        Titulo       = dados.Titulo?.Trim() ?? "",
    +        Autor        = dados.Autor?.Trim() ?? "",
    +        Genero       = dados.Genero?.Trim() ?? "",
    +        Descricao    = dados.Descricao?.Trim() ?? "",
    +        Foto         = dados.Foto?.Trim() ?? "",
    +        Keywords     = dados.Keywords?.Trim() ?? "",
    +        Ativo        = true,
    +        CriadoEm     = DateTime.Now,
    +        AtualizadoEm = default,
    +    };
    +
    +    if (livro.Isbn == "")
    +    {
    +        throw new Exception("O ISBN do livro é obrigatório.");
    +    }
    +
    +    if (livro.Titulo == "")
    +    {
    +        throw new Exception("O título do livro é obrigatório.");
    +    }
    +
    +    cmd.CommandText =
    +        @"
    +        INSERT INTO Livro
    +        (Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm)
    +        VALUES
    +        (@isbn, @titulo, @autor, @genero, @descricao, @foto, @keywords, @ativo, @criadoem, @atualizadoem)
    +        ";
    +
    +    cmd.Parameters.AddWithValue("isbn",         livro.Isbn);
    +    cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +    cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +    cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +    cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +    cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +    cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +    cmd.Parameters.AddWithValue("ativo",        livro.Ativo);
    +    cmd.Parameters.AddWithValue("criadoem",     livro.CriadoEm);
    +    cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +
    +    return livro;
    +}`}
    + +

    + O método se inicia com a abertura da conexão com o banco de dados. +

    + +
    {`var livro = new Livro
    +{
    +    Isbn         = dados.Isbn?.Trim() ?? "",
    +    Titulo       = dados.Titulo?.Trim() ?? "",
    +    Autor        = dados.Autor?.Trim() ?? "",
    +    Genero       = dados.Genero?.Trim() ?? "",
    +    Descricao    = dados.Descricao?.Trim() ?? "",
    +    Foto         = dados.Foto?.Trim() ?? "",
    +    Keywords     = dados.Keywords?.Trim() ?? "",
    +    Ativo        = true,
    +    CriadoEm     = DateTime.Now,
    +    AtualizadoEm = default,
    +};`}
    + +

    + Criamos uma variável que será uma cópia do objeto recebido no parâmetro. Fazemos isso para tratar os dados do livro, formatando-os, e assegurando + que os seus campos assumam os valores corretos. Este objeto é contém os dados que serão enviados para o banco de dados. +

    + +

    + O símbolo ? é o operador de propagação de nulos. Em uma cadeia de expressões de acesso de membros em um objeto, um propagador de nulo faz com que, se um membro for nulo, o resultado de toda a expressão seja nula. + Por exemplo, dados.Descricao é um valor do tipo string, que pode assumir um valor nulo. Na expressão dados.Descricao.Trim(), se dados.Descricao for nulo, um + NullReferenceException será lançado, pois não é possível executar um método ou acessar uma propriedade em um objeto nulo. Já na expressão dados.Descricao?.Trim(), se dados.Descricao + for nulo, como há o operador de propagação de nulos, .Trim() não será executado, e o resultado da expressão será null. +

    + +

    + De forma semelhante, o ?? age sobre dois operandos. Na expressão var x = A ?? B, a variável x irá assumir o valor de A apenas se ela não for nula. Do contrário, + x assumirá o valor de B. +

    + +

    + Portanto, na expressão dados.Descricao?.Trim() ?? "", se dados.Descricao for nulo, então a expressão ficará null ?? "", que terá como resultado a string vazia "". Ou seja, + Se dados.Descricao for nulo, livro.Descricao será uma string vazia, e nunca assumirá null. +

    + +

    + Fazemos isso nos campos do objeto livro para garantir que nenhum deles seja nulo, já que no banco de dados, todos os campos da tabela são NOT NULL. +

    + +
    {`if (livro.Isbn == "")
    +{
    +    throw new Exception("O ISBN do livro é obrigatório.");
    +}
    +
    +if (livro.Titulo == "")
    +{
    +    throw new Exception("O título do livro é obrigatório.");
    +}`}
    + +

    + Fazemos algumas validações básicas, para assegurar que os dados obrigatórios do registro tenham sido preenchidos. +

    + +
    {`cmd.CommandText =
    +    @"
    +    INSERT INTO Livro
    +    (Isbn, Titulo, Autor, Genero, Descricao, Foto, Keywords, Ativo, CriadoEm, AtualizadoEm)
    +    VALUES
    +    (@isbn, @titulo, @autor, @genero, @descricao, @foto, @keywords, @ativo, @criadoem, @atualizadoem)
    +    ";
    +
    +cmd.Parameters.AddWithValue("isbn",         livro.Isbn);
    +cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +cmd.Parameters.AddWithValue("ativo",        livro.Ativo);
    +cmd.Parameters.AddWithValue("criadoem",     livro.CriadoEm);
    +cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);`}
    + +

    + Assim como no método de listagem e da consulta por ISBN, montamos o SQL com um INSERT, e depois adicionamos os parâmetros do comando, com os + dados do objeto livro. +

    + +
    {`await cmd.ExecuteNonQueryAsync();
    +
    +return livro;`}
    + +

    + Em seguida, executamos o insert através de await cmd.ExecuteNonQueryAsync(). Como a operação é de escrita, não há resultado a ser lido. +

    + +

    + Adicionando-o ao endpoint de criação, temos: +

    + +
    {`// Cria um novo livro.
    +app.MapPost("/livros", async (Livro livro) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Criar(livro);
    +
    +    return res;
    +});`}
    + +

    Editando um livro

    + +

    + O método de edição é similar ao de criação. +

    + +
    {`public async Task Editar(string isbn, Livro dados)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    var livro = await Obter(isbn);
    +
    +    livro.Titulo       = dados.Titulo?.Trim() ?? "";
    +    livro.Autor        = dados.Autor?.Trim() ?? "";
    +    livro.Genero       = dados.Genero?.Trim() ?? "";
    +    livro.Descricao    = dados.Descricao?.Trim() ?? "";
    +    livro.Foto         = dados.Foto?.Trim() ?? "";
    +    livro.Keywords     = dados.Keywords?.Trim() ?? "";
    +    livro.AtualizadoEm = DateTime.Now;
    +
    +    cmd.CommandText =
    +        @"
    +        UPDATE Livro SET
    +        Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
    +        WHERE Isbn=@isbn
    +        ";
    +
    +    cmd.Parameters.AddWithValue("isbn",         isbn);
    +    cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +    cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +    cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +    cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +    cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +    cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +    cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +
    +    return livro;
    +}`}
    + +

    + O método recebe dois parâmetros, um isbn que identifica o livro que será editado, e um objeto com os novos dados do livro. +

    + +
    {`var livro = await Obter(isbn);
    +
    +livro.Titulo       = dados.Titulo?.Trim() ?? "";
    +livro.Autor        = dados.Autor?.Trim() ?? "";
    +livro.Genero       = dados.Genero?.Trim() ?? "";
    +livro.Descricao    = dados.Descricao?.Trim() ?? "";
    +livro.Foto         = dados.Foto?.Trim() ?? "";
    +livro.Keywords     = dados.Keywords?.Trim() ?? "";
    +livro.AtualizadoEm = DateTime.Now;`}
    + +

    + Chamamos a função Obter(string) que criamos para obter do banco de dados o livro que será editado. Em seguida, atribuímos a este objeto + os valores do objeto dados, formatando-os devidamente. +

    + +
    {`cmd.CommandText =
    +    @"
    +    UPDATE Livro SET
    +    Titulo=@titulo, Autor=@autor, Genero=@genero, Descricao=@descricao, Foto=@foto, Keywords=@keywords, AtualizadoEm=@atualizadoem
    +    WHERE Isbn=@isbn
    +    ";
    +
    +cmd.Parameters.AddWithValue("isbn",         isbn);
    +cmd.Parameters.AddWithValue("titulo",       livro.Titulo);
    +cmd.Parameters.AddWithValue("autor",        livro.Autor);
    +cmd.Parameters.AddWithValue("genero",       livro.Genero);
    +cmd.Parameters.AddWithValue("descricao",    livro.Descricao);
    +cmd.Parameters.AddWithValue("foto",         livro.Foto);
    +cmd.Parameters.AddWithValue("keywords",     livro.Keywords);
    +cmd.Parameters.AddWithValue("atualizadoem", livro.AtualizadoEm);
    +
    +await cmd.ExecuteNonQueryAsync();
    +
    +return livro;`}
    + +

    + Em seguida, montamos o comando um UPDATE, atribuímos os parâmetros, executamos a consulta e retornamos o livro que foi editado. +

    + +

    + Vamos agora chamar este método no seu endpoint. +

    + +
    {`// Edita um livro.
    +app.MapPut("/livros/{isbn}", async (string isbn, Livro livro) =>
    +{
    +    var repo = new LivroRepository();
    +    var res  = await repo.Editar(isbn, livro);
    +
    +    return res;
    +});`}
    + +

    Removendo um livro

    + +

    + Nós não iremos remover livros do banco de dados, ao invés disso, a operação de remoção irá + editar apenas o campo Ativo de um livro, alterando-o para false. + Portanto, o método de remover será basicamente uma edição. +

    + +
    {`public async Task Desativar(string isbn)
    +{
    +    using var conn = new MySqlConnection(ConnString);
    +    using var cmd  = conn.CreateCommand();
    +
    +    await conn.OpenAsync();
    +
    +    cmd.CommandText = "UPDATE Livro SET Ativo=false WHERE Isbn=@isbn";
    +    cmd.Parameters.AddWithValue("isbn", isbn);
    +
    +    await cmd.ExecuteNonQueryAsync();
    +}`}
    + +

    + Vamos adicioná-lo agora ao seu endpoint. +

    + +
    {`// Remove um livro.
    +app.MapDelete("/livros/{isbn}", async (string isbn) =>
    +{
    +    var repo = new LivroRepository();
    +
    +    await repo.Desativar(isbn);
    +});`}
    + +

    Finalizando

    + +

    + Implementamos todas as operações básicas de manipulação dos registros, e os adicionamos aos endpoints da nossa API. Mais ainda precisamos ajustar algumas coisas.

    + \ No newline at end of file diff --git a/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte b/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte index 0cd187f..bc3415b 100644 --- a/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/endpoints_2/+page.svelte @@ -24,7 +24,6 @@

    diff --git a/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte b/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte index 13a419b..1f8a111 100644 --- a/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte +++ b/material-sistemas-distribuidos/src/routes/projeto_de_api/+page.svelte @@ -19,7 +19,7 @@