LoginSignup
0
1

More than 1 year has passed since last update.

SQLite3のデータをマルチマスター構成でレプリケーションしてみる

Last updated at Posted at 2023-01-25

前回はSQLite3のデータをWeb APIで操作してみましたが、今回はマルチマスター構成でレプリケーションしてみようと思います。
https://qiita.com/namikitakeo/items/42fa3e42c2c956029bb0

RaspberryPi_1(マスター1)
GETリクエストはそのままで、POST/PUT/DELETEリクエストAPIをブロックとして他ノードに転送します。

RaspberryPi_2(マスター2)
GETリクエストはそのままで、POST/PUT/DELETEリクエストAPIをブロックとして他ノードに転送します。

WEBAPI起動時に、他ノードからAPIブロックチェーンを取得します。取得失敗した場合は、GENESISブロックを生成します。
POST/PUT/DELETEリクエストAPIをブロックとして、ブロックチェーンに追加します。オンメモリーで改竄がむずかしいため、PreviousHashは前ブロックのIdとします。

Models/Pizza.cs
using Microsoft.EntityFrameworkCore;

namespace PizzaStore.Models
{
    public class PizzaStoreSetting
    {
        public string BaseUrl { get; set; }
    }
    public class PizzaChain
    {
        public string Id { get; set; }
        public string PreviousHash { get; set; }
        public string Timestamp { get; set; }
        public string Block { get; set; }
    }
    public class PizzaRequest
    {
        public string id { get; set; }
        public string name { get; set; }
        public string description { get; set; }
    }
    public class Pizza
    {
        public string? Id { get; set; }
        public string? Name { get; set; }
        public string? Description { get; set; }
    }
    class PizzaDb : DbContext
    {
        public PizzaDb(DbContextOptions options) : base(options) { }
        public DbSet<Pizza> Pizzas { get; set; } = null!;
    }
}
Program.cs
using PizzaStore.Models;
using System.Net.Http.Headers;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

// GENESIS
string phash = "19990101000000";
List<PizzaChain> pizzal = new List<PizzaChain>();
// RESTORE
var builder = WebApplication.CreateBuilder(args);
var settings = builder.Configuration.GetSection("PizzaStoreSetting").Get<PizzaStoreSetting>();
HttpClient client = new HttpClient();
try {
    if (settings is null) {
        pizzal.Add(new PizzaChain {Id = phash , PreviousHash = "00000101000000", Timestamp = "1999/01/01 00:00:00", Block = "GENESIS"});
    } else {
        client.BaseAddress = new Uri(settings.BaseUrl);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(
"application/json"));
        pizzal = await client.GetFromJsonAsync<List<PizzaChain>>("/blocks");
        phash = pizzal[pizzal.Count-1].Id;
    }
} catch(Exception e) {
    pizzal.Add(new PizzaChain {Id = phash , PreviousHash = "00000101000000", Timestamp = "1999/01/01 00:00:00", Block = "GENESIS"});
}
// EF WEB
var connectionString = builder.Configuration.GetConnectionString("Pizzas") ?? "Data Source=Pizzas.db";
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSqlite<PizzaDb>(connectionString);
var app = builder.Build();
// INSERT
app.MapPost("/pizza", async (PizzaDb db, Pizza pizza) =>
{
    PizzaRequest body = new PizzaRequest();
    body.id = pizza.Id;
    body.name = pizza.Name;
    body.description = pizza.Description;
    DateTime dt = DateTime.Now;
    PizzaChain pizzac = new PizzaChain {Id =  dt.ToString("yyyyMMddHHmmss"), PreviousHash = phash, Timestamp = dt.ToString("yyyy/MM/dd HH:mm:ss"), Block = "POST /pizza [id="+body.id+",name="+body.name+",description="+body.description+"]"};
    phash = dt.ToString("yyyyMMddHHmmss");
    if (settings is  null) {
        await db.Pizzas.AddAsync(pizza);
        await db.SaveChangesAsync();
        pizzal.Add(pizzac);
    } else {
        HttpClient client = new HttpClient();
        try {
            client.BaseAddress = new Uri(settings.BaseUrl);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = await client.PostAsJsonAsync("/block", pizzac);
            pizzal.Add(pizzac);
            await db.Pizzas.AddAsync(pizza);
            await db.SaveChangesAsync();
        } catch(Exception e) {
            Console.WriteLine("POST NG");
        }
    }
    return Results.Created($"/pizza/{pizza.Id}", pizza);
});
// UPDATE
app.MapPut("/pizza/{id}", async (PizzaDb db, Pizza updatepizza, string id) =>
{
    var pizza = await db.Pizzas.FindAsync(id);
    if (pizza is null) return Results.NotFound();
    PizzaRequest body = new PizzaRequest();
    body.name = updatepizza.Name;
    body.description = updatepizza.Description;
    DateTime dt = DateTime.Now;
    PizzaChain pizzac = new PizzaChain {Id =  dt.ToString("yyyyMMddHHmmss"), PreviousHash = phash, Timestamp = dt.ToString("yyyy/MM/dd HH:mm:ss"), Block = "PUT /pizza/"+id+" [name="+body.name+",description="+body.description+"]"};
    phash = dt.ToString("yyyyMMddHHmmss");
    if (settings is  null) {
        pizza.Name = updatepizza.Name;
        pizza.Description = updatepizza.Description;
        await db.SaveChangesAsync();
        pizzal.Add(pizzac);
    } else {
        HttpClient client = new HttpClient();
        try {
            client.BaseAddress = new Uri(settings.BaseUrl);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = await client.PostAsJsonAsync("/block", pizzac);
            pizza.Name = updatepizza.Name;
            pizza.Description = updatepizza.Description;
            await db.SaveChangesAsync();
            pizzal.Add(pizzac);
        } catch (Exception e) {
            Console.WriteLine("PUT NG");
        }
    }
    return Results.NoContent();
});
// DELETE
app.MapDelete("/pizza/{id}", async (PizzaDb db, string id) =>
{
    var pizza = await db.Pizzas.FindAsync(id);
    if (pizza is null)
    {
       return Results.NotFound();
    }
    DateTime dt = DateTime.Now;
    PizzaChain pizzac = new PizzaChain {Id =  dt.ToString("yyyyMMddHHmmss"), PreviousHash = phash, Timestamp = dt.ToString("yyyy/MM/dd HH:mm:ss"), Block = "DELETE /pizza/"+id};
    phash = dt.ToString("yyyyMMddHHmmss");
    if (settings is  null) {
        db.Pizzas.Remove(pizza);
        await db.SaveChangesAsync();
        pizzal.Add(pizzac);
    } else {
        HttpClient client = new HttpClient();
        try {
            client.BaseAddress = new Uri(settings.BaseUrl);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = await client.PostAsJsonAsync("/block", pizzac);
            db.Pizzas.Remove(pizza);
            await db.SaveChangesAsync();
            pizzal.Add(pizzac);
        } catch(Exception e) {
            Console.WriteLine("DELETE NG");
        }
    }
    return Results.Ok();
});
// SELECT
app.MapGet("/pizza/{id}", async (PizzaDb db, string id) => Results.Json(await db.Pizzas.FindAsync(id), new JsonSerializerOptions { WriteIndented = true }));
// SELECT ALL
// app.MapGet("/pizzas", async (PizzaDb db) => await db.Pizzas.ToListAsync());
app.MapGet("/pizzas", async (PizzaDb db) => Results.Json(await db.Pizzas.ToListAsync(), new JsonSerializerOptions { WriteIndented = true }));
// BLOCKS
app.MapGet("/blocks", async (PizzaDb db) =>
{
    return JsonSerializer.Serialize(pizzal, new JsonSerializerOptions { WriteIndented = true });
});
// BLOCK CHAIN
app.MapPost("/block", async (PizzaDb db, PizzaChain pizza) =>
{
    if (pizza.Block.StartsWith("POST ")) {
        int i = pizza.Block.IndexOf("id=");
        int n = pizza.Block.IndexOf("name=");
        int d = pizza.Block.IndexOf("description=");
        int l = pizza.Block.Length;
        string id = pizza.Block.Substring(i+3, n-i-4);
        string name = pizza.Block.Substring(n+5, d-n-6);
        string desc = pizza.Block.Substring(d+12, l-d-13);
        await db.Pizzas.AddAsync(new Pizza {Id = id, Name = name, Description = desc});
        await db.SaveChangesAsync();
        pizzal.Add(pizza);
        phash = pizza.Id;
    } else if (pizza.Block.StartsWith("PUT ")) {
        string id = pizza.Block.Substring(0, pizza.Block.IndexOf(" ["));
        id = id.Substring(id.Length-14);
        Console.WriteLine("|"+id+"|");
        int n = pizza.Block.IndexOf("name=");
        int d = pizza.Block.IndexOf("description=");
        int l = pizza.Block.Length;
        string name = pizza.Block.Substring(n+5, d-n-6);
        string desc = pizza.Block.Substring(d+12, l-d-13);
        var temp = await db.Pizzas.FindAsync(id);
        if (temp is not null) {
            temp.Name = name;
            temp.Description = desc;
            await db.SaveChangesAsync();
        }
        pizzal.Add(pizza);
        phash = pizza.Id;
    } else if (pizza.Block.StartsWith("DELETE ")) {
        string id = pizza.Block.Substring(pizza.Block.Length-14);
        var temp = await db.Pizzas.FindAsync(id);
        if (temp is not null)
        {
            db.Pizzas.Remove(temp);
            await db.SaveChangesAsync();
        }
        pizzal.Add(pizza);
        phash = pizza.Id;
    }
});
app.Run();
appsettings.json
{
  "PizzaStoreSetting": {
    "BaseUrl": "http://RaspberryPi_2:5033"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Properties/launchSettings.json
{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:19381",
      "sslPort": 44341
    }
  },
  "profiles": {
    "PizzaStore": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://RaspberryPi_1:7006;http://RaspberryPi_1:5033",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1