| name | accordant-state |
| description | How to design state models in Accordant - use this skill when defining what your system tracks between operations |
Designing State in Accordant
State is what an external observer needs to know to predict what an operation should return. Keep it minimal — only track what's necessary to define correct behavior.
The [State] Attribute
Use [State] on a partial class to get automatic cloning, equality, and hashing:
[State]
public partial class BankState
{
public Dictionary<string, decimal> Accounts { get; set; } = new();
}
The source generator handles:
- Deep cloning for immutable state transitions
- Equality comparison for state deduplication
- Hashing for state graph exploration
State Design Principles
Keep It Minimal
Ask: "Does any operation need this to determine its response?" If no, leave it out.
[State]
public partial class BadState
{
public Dictionary<string, AccountEntity> Accounts { get; set; }
public DateTime LastModified { get; set; }
public string ConnectionString { get; set; }
}
[State]
public partial class GoodState
{
public Dictionary<string, decimal> Accounts { get; set; } = new();
}
Nested State Classes
For complex domains, nest state classes:
[State]
public partial class AppState
{
public Dictionary<string, UserState> Users { get; set; } = new();
}
[State]
public partial class UserState
{
public string Name { get; set; } = string.Empty;
public Dictionary<string, TodoState> Todos { get; set; } = new();
}
[State]
public partial class TodoState
{
public string Title { get; set; } = string.Empty;
public bool Completed { get; set; } = false;
}
State Transitions
Operations return expected outcomes that describe both the response and the next state.
No State Change: .SameState()
For errors or read-only operations:
if (!state.Accounts.ContainsKey(accountId))
return Expect.That<ApiResult<decimal>>(r => r.IsNotFound).SameState();
return Expect.That<ApiResult<decimal>>(r => r.IsSuccess && r.Data == balance).SameState();
State Changes: .ThenState()
The lambda receives a clone of the current state. Modify it directly:
return Expect.That<ApiResult<decimal>>(r => r.IsSuccess && r.Data == 0)
.ThenState<BankState>(nextState => nextState.Accounts[accountId] = 0);
Convention: Always name the parameter nextState for clarity.
Response-Dependent State
When state depends on server-generated values (IDs, timestamps):
return Expect.That<ApiResult<Order>>(r => r.IsSuccess && r.Data.OrderId != null)
.ThenState<AppState>(
(ApiResult<Order> response, AppState nextState) =>
nextState.Orders[response.Data.OrderId] = new OrderState { },
mock: () => new ApiResult<Order> { Data = new Order { OrderId = Guid.NewGuid().ToString() } }
);
The mock: provides a plausible response for test generation (when no real system is running).
Supported Property Types
| Type | Notes |
|---|
| Primitives | int, string, decimal, bool, DateTime, Guid, etc. |
| Collections | Dictionary<K,V>, List<T>, HashSet<T> |
| Nested [State] | Other classes marked with [State] |
| Enums | Any enum type |
Dictionary keys: Supported types are string, int, long, and Guid. Keys are sorted during serialization to ensure consistent hashing.
[SharedState] for Large Values
For large values where cloning would be expensive:
[State]
public partial class ImageState
{
public string Name { get; set; }
[SharedState(nameof(ContentFingerprint))]
public List<byte> Content { get; set; }
public string ContentFingerprint => Content == null
? null
: Convert.ToHexString(SHA256.HashData(Content.ToArray()));
}
Important: Shared values are shallow-copied, so treat them as immutable. Never modify after setting.
Common Mistakes
Mutable State Access
return Expect.That(r => r.IsSuccess)
.ThenState<AppState>(nextState => {
state.Users[id] = newUser;
return nextState;
});
return Expect.That(r => r.IsSuccess)
.ThenState<AppState>(nextState => nextState.Users[id] = newUser);
Overly Complex State
[State]
public partial class TooMuchState
{
public List<AuditEntry> AuditLog { get; set; }
public Dictionary<string, List<HistoricalBalance>> BalanceHistory { get; set; }
}
[State]
public partial class JustRightState
{
public Dictionary<string, decimal> Balances { get; set; }
}
Next Steps
- Operations: Learn to write Apply methods that use state
- Test Generation: See how state complexity affects test count