通過改變業務模型的預留模式避免分散式事務 - CodeOpinion

banq發表於2022-07-05

長時間執行的業務流程可能會持續幾秒鐘到幾天,您無法使用分散式事務鎖定服務中的資源。那麼有什麼選擇呢?現實世界有一個解決方案,它是一種預訂保留。
預訂模式允許您獲得有時限的有限保證,允許您與其他服務進行協調。

預訂模式一直在現實世界中使用。我們可以利用程式碼中的模式為長期執行的業務流程在服務之間提供有時限的有限保證。這是一個可以過期的有限鎖。

例如我在網上下了一個取貨訂單。該物品是為我保留的,這可以防止我去商店浪費時間並且該物品不可用。當員工從貨架上拿下物品時,該預訂有 7 天時間讓我取貨,否則該預訂將過期,其他人可以購買。
有時您只需要在現實世界(業務領域)中尋找解決方案。

預訂的一個重要方面是有時間限制。您會在電子郵件中注意到,如果我沒有在 7 天內取貨,他們將退還我的信用卡並將商品放回貨架上。
他們正在為我的物品提供鎖定/保留,該鎖定將在 7 天內到期。
當我去當地商店取貨時,這是確認我的預訂。

預訂模式
我們可以在程式碼中實現預訂模式來解決類似型別的問題。在使用者註冊的示例中,我們可以在註冊過程開始時在使用者名稱上建立預訂。一旦使用者完成使用者註冊過程,我們就可以確認預訂。
如果使用者從未完成註冊過程,則使用者名稱將在我們定義的時間後過期,讓其他人嘗試註冊相同的使用者名稱。
預訂模式有 3 個重要方面。保留、確認、到期。
為了首先在程式碼中說明這一點,我將首先展示同步版本,然後是非同步版本,這通常更適用。
使用者註冊首先檢查使用者名稱的保留是否存在。如果沒有,它將建立一個新帳戶,將其儲存到我們的資料庫中,然後告訴預訂它已完成。

public class UserRegistration
{
private static int _testCount = 0;
private readonly UsernameReservationSync _reservation;
private readonly IUserRepository _repository;

public UserRegistration(UsernameReservationSync reservation, IUserRepository repository)
{
    _reservation = reservation;
    _repository = repository;
}

public bool Register(string username)
{
    if (_reservation.Reserve(username) == false)
    {
        return false;
    }

    // For testing to show the expiry
    if (username == "test" && _testCount == 0)
    {
        _testCount++;
        return false;
    }

    var account = new Account(username);
    _repository.Add(account);
    _repository.Save();

    if (_reservation.Complete(username) == false)
    {
        _repository.Remove(account);
        return false;
    }

    return true;
}


預留本身具有預留、完成的能力。在內部 5 秒後,如果仍然保留,它將使使用者名稱過期。這是一個示例,沒有使用真實的資料庫,也沒有像在真實的生產環境中那樣處理併發。

public class UsernameReservationSync
{
    private TimeSpan Timeout => TimeSpan.FromSeconds(5);
    private readonly FakeDatabase _db;

    public UsernameReservationSync(FakeDatabase db)
    {
        _db = db;
    }

    public bool Reserve(string username)
    {
        if (_db.RegisteredUsernames.Any(x => x == username))
        {
            return false;
        }
        if (_db.ReservedUsernames.Any(x => x == username))
        {
            return false;
        }

        _db.ReservedUsernames.Add(username);

        Task.Run(async () =>
        {
            await Task.Delay(Timeout);
            Expire(username);
        });

        return true;
    }

    private void Expire(string username)
    {
        _db.ReservedUsernames.Remove(username);
    }

    public bool Complete(string username)
    {
        if (_db.ReservedUsernames.Any(x => x == username) == false)
        {
            return false;
        }
        if (_db.RegisteredUsernames.Any(x => x == username))
        {
            return false;
        }

        _db.ReservedUsernames.Remove(username);
        _db.RegisteredUsernames.Add(username);

        return true;
    }
}

詳細點選標題

相關文章