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
Vault FactoryVaultFactoryCreates the correct vault instance
Vault RepositoryVaultRepositoryGroups vaults by keyspace

Note:

Most users only interact with VaultRepository directly.

Using Vaults

Inject a VaultRepository

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

    public MyService(IVault<Player> 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();

Saving Data

await _playerVault.SaveAsync(player);

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

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.