16. 존 간 플레이어/아이템 이전 (서버-서버 분산 트랜잭션)

난이도 최상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 존 간 플레이어/아이템 이전 (서버-서버 분산 트랜잭션)
// ----------------------------------------------------------------------------
// 시나리오 (서버-서버 / 분산):
//   - 플레이어가 존 서버 A 에서 존 서버 B 로 이동(심리스 경계/포탈/채널이동)할 때,
//     캐릭터 상태(골드, 인벤토리 아이템)를 A 에서 B 로 이전한다.
//   - A 는 자기 메모리/DB 에서 플레이어를 제거하고, B 는 그 상태로 플레이어를
//     생성한다. 두 서버는 네트워크 메시지로만 통신하며, 메시지는 유실/지연/중복
//     될 수 있고 어느 한쪽이 중간에 죽을 수 있다.
//   - 같은 골드/아이템이 두 서버에 동시에 존재하면 복제(dupe) 버그가 되고,
//     이전 중 실패로 양쪽 모두에서 사라지면 자산 유실이 된다.
//
// 요구사항:
//   - 이전은 원자적이어야 한다: 골드/아이템은 정확히 한 곳에만 존재(복제·유실 금지).
//   - 메시지 유실/중복/서버 다운 어느 경우에도 자산이 복제되거나 사라지면 안 된다.
//   - 같은 이전 요청이 재시도돼도 결과는 한 번 적용된 것과 같아야 한다(멱등).
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 복제·유실·중복이 발생하는지
//   설명하고, 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class PlayerState
{
    public long              PlayerId;
    public long              Gold;
    public List<int>         Items = new List<int>();
}

public interface IZoneLink
{
    // 다른 존 서버로 "플레이어 받아라" 메시지 전송 (네트워크). fire-and-forget.
    Task SendTransfer(int targetZoneId, PlayerState state);
}

// 존 서버 A 측: 플레이어를 B 로 떠나보내는 로직
public class ZoneTransferOut
{
    private readonly Dictionary<long, PlayerState> _players;
    private readonly IZoneLink _link;

    public ZoneTransferOut(Dictionary<long, PlayerState> players, IZoneLink link)
    {
        _players = players;
        _link = link;
    }

    // 플레이어가 B 존으로 이동 요청
    public async Task TransferTo(long playerId, int targetZoneId)
    {
        PlayerState st = _players[playerId];           // (A) 현재 상태 읽기

        // (B) 대상 서버로 상태 전송
        await _link.SendTransfer(targetZoneId, st);

        // (C) 로컬에서 플레이어 제거 (B 로 넘어갔으니)
        _players.Remove(playerId);
    }
}

// 존 서버 B 측: 이전 메시지를 받아 플레이어를 생성
public class ZoneTransferIn
{
    private readonly Dictionary<long, PlayerState> _players;

    public ZoneTransferIn(Dictionary<long, PlayerState> players)
    {
        _players = players;
    }

    // A 로부터 SendTransfer 수신 시 호출
    public void OnTransferReceived(PlayerState state)
    {
        // (D) 받은 상태로 플레이어 생성
        _players[state.PlayerId] = state;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.