iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0

Hi all, 今天第九天 今天就來讓專案可以 Insert Block吧!

那由於主要內容都是與SQL做互動,所以我們的code 主要也會動在 repository 層的 InsertBlock(BlockDomain)

Repository

首先我們必須在 Repository 注入一個與資料庫連線的類別 DbContext ,code 如下


public class ChainRepository(BlockchainDbContext blockchainDbContext): IChainRepository
{
    private DbSet<Block> _blocks = blockchainDbContext.Blocks;

    public async Task InsertBlock(BlockDomain newBlockDomain)
    {
        await _blocks.AddAsync(newBlockDomain.ToEntity());
        await blockchainDbContext.SaveChangesAsync();
    }
}

這樣一來,我們就可以成功在 Table裡頭新增資料了。但還有個問題,那就是在 Service 層的新增區塊的流程 code 如下

public async Task<BlockDomain> GenerateNewBlock(GenerateNewBlockDto dto)
{
    var firstBlock = chainRepository.GetBlockBy(0);
    var newBlock = firstBlock.GenerateNextBlock(dto, Nonce);
    
    await chainRepository.InsertBlock(newBlock);
    return newBlock;
}

Update Service Code

可以看到我們在計算 newBlock 是透過 id 為 0 的區塊計算出來的,但這不是我們期望的,我們希望的是取得目前鏈上的最後一塊區塊對吧? 所以我們必須要來小修改下,當然這邊也會是由 TDD 出發,code 如下:

Unit Test Code

    [Test]
    public async Task should_not_request_block_when_the_length_is_0()
    {
        _chainRepository!.GetChainLength().Returns(0);
        await _chainService.GenerateNewBlock(new GenerateNewBlockDto()
        {
            Data = "new",
            TimeStamp = DateTime.Now
        });

        _chainRepository.DidNotReceive().GetBlockBy(Arg.Any<int>());
        await _chainRepository.Received().InsertBlock(Arg.Is<BlockDomain>(b => b.Data == "Genesis Block"));
    }

Production Code

    public async Task<BlockDomain> GenerateNewBlock(GenerateNewBlockDto dto)
    {
        if (await chainRepository.GetChainLength() == 0)
        {
            var genesisBlock = new BlockDomain
            {
                Data = "Genesis Block",
                Hash = "0",
                PreviousHash = "0",
                TimeStamp = DateTime.Now,
                Nonce = 0
            };
            await chainRepository.InsertBlock(genesisBlock);
            return genesisBlock;
        }
        var firstBlock = chainRepository.GetBlockBy(0);
        
        var newBlock = firstBlock.GenerateNextBlock(dto, Nonce);
        
        await chainRepository.InsertBlock(newBlock);
        return newBlock;
    }

接著我們還可以再寫下個 test case → 鏈長度不為 0的時候

Unit Test Code

    [Test]
    public async Task should_generate_new_block_base_on_latest_block()
    {
        _chainRepository!.GetChainLength().Returns(1);

        GivenBlock(new BlockDomain
        {
            Hash = "123",
        });

        var newBlock = await _chainService.GenerateNewBlock(new GenerateNewBlockDto()
        {
            Data = "new",
            TimeStamp = DateTime.Now
        });

        _chainRepository.Received().GetBlockBy(Arg.Is<int>(i => i==1-1));
        newBlock.PreviousHash.Should().Be("123");
    }

Production Code

    public async Task<BlockDomain> GenerateNewBlock(GenerateNewBlockDto dto)
    {
        var chainLength = await chainRepository.GetChainLength();

        if (chainLength == 0)
        {
            var genesisBlock = new BlockDomain
            {
                Data = "Genesis Block",
                Hash = "0",
                PreviousHash = "0",
                TimeStamp = DateTime.Now,
                Nonce = 0
            };
            await chainRepository.InsertBlock(genesisBlock);
            return genesisBlock;
        }
        
        var firstBlock = chainRepository.GetBlockBy(chainLength-1);
        var newBlock = firstBlock.GenerateNextBlock(dto, Nonce);
        
        await chainRepository.InsertBlock(newBlock);
        return newBlock;

該有的邏輯都完成了,就來重構下吧!

Refactor Code

public async Task<BlockDomain> GenerateNewBlock(GenerateNewBlockDto dto)
{
    var chainLength = await chainRepository.GetChainLength();
    var newBlock = chainLength == 0
        ? new BlockDomain
        {
            Data = "Genesis Block",
            Hash = "0",
            PreviousHash = "0",
            TimeStamp = DateTime.Now,
            Nonce = 0
        }
        : chainRepository.GetBlockBy(chainLength - 1).GenerateNextBlock(dto, Nonce);

    await chainRepository.InsertBlock(newBlock);
    return newBlock;
}

以上我們就完成了,我們的新需求:基於上一個區塊的 hash值進行計算。

題外話:Get Block By

如果記憶力好的話,我們還有個 GetBlockBy 還沒有實作,這就來實作個,code 如下

public async Task<BlockDomain> GetBlockBy(int id)
{
    var block = await _blocks.FirstAsync(x=>x.Id == id);
    return block.ToDomain();
}

E2e Test

如此一來,系統就可以好好運行啦,來個 E2E Test下

First Block

image.png

Second Block

image.png

Conclusion

總算是打通與DB的連線了~~~ 接著我們就可以往可編輯 的方向走了,但這是明天的事情 ,明天再說**

結語:明天上班 🫠🫠🫠🫠🫠


上一篇
Day08 .Net EntityFramework
下一篇
Day10 Introduce Chameleon Hash
系列文
Side-Project:: 為自己打造個可編輯的區塊鏈30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言