Vault System

A Vault is a flexible data access API backed by a persistence provider. It abstracts database operations behind a consistent interface, allowing you to interact with stored data without coupling to a specific database.

Configuration

altruist:
  persistence:
    database:
      provider: postgres
      host: localhost
      port: 5432
      username: myuser
      password: mypassword
      database: mydb
    cache:
      provider: inmemory

The Vault Hierarchy

LayerInterface / ClassDescription
Database ProviderIGeneralDatabaseProviderDirect database interaction (CRUD)
Vault APISqlVaultAbstracts queries using LINQ-like syntax
Injected Vault ContractIVault<T>The public API you use for queries, saves, updates, deletes, and history
Vault RegistryVaultRegistryRuntime registry for metadata and dynamic vault lookup

Note:

Most applications should inject IVault<T> directly. You do not need a vault repository or repository factory in normal usage.

Using Vaults

Inject IVault<T>

[Service]
public class MyService
{
    private readonly IVault<PlayerVault> _playerVault;

    public MyService(IVault<PlayerVault> playerVault)
    {
        _playerVault = playerVault;
    }
}

Querying Data

var players = await _playerVault.Where(p => p.Level > 10).ToListAsync();
var player = await _playerVault.Where(p => p.Name == "Hero").FirstOrDefaultAsync();
var exists = await _playerVault.AnyAsync(p => p.Name == "Hero");
var count = await _playerVault.CountAsync();

Saving Data

await _playerVault.SaveAsync(player);

// With historical snapshot
await _playerVault.SaveAsync(player, saveHistory: true);

Updating and deleting

await _playerVault
    .Where(p => p.StorageId == playerId)
    .UpdateAsync(setters => setters.SetProperty(p => p.Level, p => p.Level + 1));

await _playerVault
    .Where(p => p.StorageId == playerId)
    .DeleteAsync();

Vault Models

Define your data models with vault attributes:

[Vault("players")]
[PrimaryKey("StorageId")]
public class Player : IVaultModel
{
    public string StorageId { get; set; } = Guid.NewGuid().ToString();

    [Column("name")]
    public string Name { get; set; } = "";

    [ColumnIndex]
    public int Level { get; set; }

    [Ignore]
    public string TemporaryData { get; set; } = "";
}

See Unified Object Model for the full attribute reference.

Transactions

Use the [Transactional] attribute on service methods to wrap all vault operations inside a database transaction:

[Service]
public class TradeService
{
    private readonly IVault<PlayerVault> _players;
    private readonly IVault<ItemVault> _items;

    public TradeService(IVault<PlayerVault> players, IVault<ItemVault> items)
    {
        _players = players;
        _items = items;
    }

    [Transactional]
    public async Task TransferItem(string fromPlayerId, string toPlayerId, string itemId)
    {
        var item = await _items.Where(i => i.StorageId == itemId).FirstOrDefaultAsync();
        if (item == null) throw new InvalidOperationException("Item not found");

        // Both operations succeed or both roll back
        item.OwnerId = toPlayerId;
        await _items.SaveAsync(item);

        var fromPlayer = await _players.Where(p => p.StorageId == fromPlayerId).FirstOrDefaultAsync();
        fromPlayer!.Gold += 100;
        await _players.SaveAsync(fromPlayer);
    }
}

If any operation throws an exception, the entire transaction rolls back — neither the item transfer nor the gold update persists.

You can specify the isolation level:

[Transactional(IsolationLevel.Serializable)]
public async Task CriticalOperation() { ... }

[Transactional(IsolationLevel.ReadCommitted)]  // Default
public async Task NormalOperation() { ... }

Note:

[Transactional] only works on methods in [Service]-registered classes. The framework intercepts the method call to manage the transaction lifecycle.