planetarium / NineChronicles.Headless

A headless node of NineChronicles game network, powered by Libplanet.
https://planetarium.github.io/NineChronicles.Headless/
GNU Affero General Public License v3.0
36 stars 41 forks source link

Refactor with repository pattern #2540

Closed moreal closed 1 month ago

moreal commented 2 months ago

Motivation

This pull request adds the repository pattern. Specifically, it will abstract and incrementally improve the behavior of BlockChain so that BlockChain, TxPool, etc. can be accessed through a repository instead of directly.

This is because we found a problem while working on something else: right now we're using BlockChain directly, so it's hard to test "under what conditions" we want the headless to behave that way. This is because the only way to manipulate the state of BlockChain is to do transactions, create blocks, and expect them to change the state. That's well tested in the Libplanet project, but testing headless implies that as well. This will not only make writing tests difficult, but it will also make the tests take longer to run.

That's why we want to gradually refactor to the repository pattern. I'd appreciate your feedback.

Overview

Repositories

I introduce several new repositories in this pull request. The purpose of the repositories is to separate BlockChain logic (in general HTTP server, Database) and GraphQL representation (in general HTTP server Controller). This makes testing easier because the GraphQL layer only depends on repositories in some parts.

Below are the new repository names and descriptions:

Testing with mocking

Since this pull request, you don't have to setup any NineChroniclesNodeService and any BlockChain to setup states that you want. You can just mock IWorldStateRepository.

You can manually mock like the below example code:

var worldState = new World(MockUtil.MockModernWorldState)
  .SetLegacyState();
var stateRootHash = worldState.Trie.Hash;
var tip = new Domain.Model.BlockChain.Block(
    BlockHash.FromString("YOUR_BLOCK_HASH"),
    BlockHash.FromString("YOUR_PREVIOUS_BLOCK_HASH"),
    default(Address),  // The block proposer (i.e., miner)
    0,  // The block index.
    Timestamp: DateTimeOffset.UtcNow,
    StateRootHash: stateRootHash,
    Transactions: ImmutableArray<Transaction>.Empty  // Append transactions if you need.
);
BlockChainRepository.Setup(repository => repository.GetTip())
    .Returns(tip);
WorldStateRepository.Setup(repository => repository.GetWorldState(stateRootHash))
    .Returns(worldState);

But if your test don't care any block data and care only states on tip, you can use GraphQLTestBase.SetupStatesOnTip:

// Setup GoldCurrencyState with Currencies.Crystal and mint Currencies.Crystal
SetupStatesOnTip(world => world
    .SetLegacyState(Addresses.GoldCurrency, new GoldCurrencyState(Currencies.Crystal).Serialize())
    .MintAsset(new ActionContext(), userAddress, Currencies.Crystal * 10));

// Setup AvatarState
SetupStatesOnTip(world => world
    .SetAvatarState(avatarAddress, avatarState));
ipdae commented 1 month ago

DevEx 테스트가 실패하는데 괜찮은건가요?

moreal commented 1 month ago

DevEx 테스트가 실패하는데 괜찮은건가요?

@ipdae 테스트 관련해서 깨지는 것이 아닌 Roslyn 컴파일러 쪽에서 발생하고 있습니다. 최근 계속 발생하고 있는데 원인을 파악하지 못 해 재시도(rerun) 하는 것으로 넘어가고 있습니다

image
moreal commented 1 month ago

https://github.com/planetarium/NineChronicles.Headless/issues/2582 이슈로 만들어 놓았습니다

moreal commented 1 month ago

Could you review this pull request, when you have time? @ipdae @U-lis