Skip to main content

Aggregates

1. What this document is about

This document explains Aggregates as the primary mechanism for enforcing domain consistency boundaries in Domain-Driven Design.

It focuses on:

  • how aggregates define transactional and consistency limits
  • how business invariants are enforcec by construction, not convention
  • how aggregates behave under **concurrency, scale and evolution
  • how to implement aggregates correctly in C# / .NET without leaking infrastructure converns

This document applies when:

  • business rules require strong consistency
  • multiple domain objects must change atomically
  • the sytem experiences concurrent writes
  • correctness matterns more than raw throughput

This document does not apply when:

  • the domain is CRUD-only
  • invariants are trivial or nonexistent
  • eventual consistency is acceptable everywhere
  • the system is real-heavy with rare writes

2. Why this matters in real systems

Aggregates appear when simple models stop working.

Typical pressures that force aggregates to exist:

  • Concurrent updates to related data
  • Partial failures leaving the system in invalid states
  • Distributed workflows that span multiple services
  • Schema and rule evolution over time
  • Regulatory or financial correctness

What breaks when aggregates are ignores:

  • invariants enforced "in the service layer"
  • rules duplicated across handlers
  • race conditions hidden behind ORM abstractions
  • data that is technically valid but business-invalid
  • retries that corrupt state instead of fixing it

Why simpler approaches fail:

  • CRUD models assume independence
  • ORMs do not understand business rules
  • transactions alone do not encode intent
  • validation outside the model is optional — and will be skipped

Aggregates exist because the database cannot protect the domain for you.


3. Core concept (mental model)

Think of an aggregate as:

A consistency bubble enforced by code

Inside the bubble:

  • invariant must always hold
  • changes are atomic
  • rules are synchronous and deterministic

Outside the bubble:

  • everything is eventually consistent
  • other aggregates are referenced by identity only
  • coordination happens asynchronously

Metal model: the gatekeeper

  • The Aggregate Root is the only gate
  • All state changes pass through it
  • If a rule is violated, the change is rejected
  • No external code is trusted to "do the right thing"

If something must be consistent now, it lives in the same aggregate.

If it can be consistent later, it does not.


4. How it works (step-by-step)

  1. Define the invariant

    • What must never be broken?
    • What combinations of stage are illegal?
  2. Choose the aggregate boundary

    • Smallest possible set of entities required to enforce invariants
    • Anything else is explicitly outside
  3. Expose behavior, not setters

    • State changes only via methods
    • Methods express domain intent
  4. Validate invariants synchronously

    • Fail fast
    • No partial success
  5. Persist the aggregate atomically

    • One transaction
    • One aggregate instance
  6. Communicate changes outward

    • Domain events
    • Integration events
    • Never direct cross-aggregate mutation

Assumptions and invariants

  • An aggregate is modified by one transaction at a time
  • External consistency is eventual
  • Invariants are non-negotiable
  • Performance trade-offs are accepted consciously

5. Minimal but realistic example (.NET)

Domain primitives (Entity / AggregateRoot / Exceptions)

using System.Collections.ObjectModel;

public abstract class Entity<TId>
where TId : notnull
{
public TId Id { get; protected set; } = default!;

// Identity-based equality
public override bool Equals(object? obj)
=> obj is Entity<TId> other &&
EqualityComparer<TId>.Default.Equals(Id, other.Id);

public override int GetHashCode()
=> Id?.GetHashCode() ?? 0;
}

public abstract class AggregateRoot<TId> : Entity<TId>
where TId : notnull
{
private readonly List<IDomainEvent> _domainEvents = new();

// Optimistic concurrency version. EF will map it as a concurrency token.
public int Version { get; private set; } = 0;

public IReadOnlyCollection<IDomainEvent> DomainEvents
=> new ReadOnlyCollection<IDomainEvent>(_domainEvents);

protected void AddDomainEvent(IDomainEvent @event)
=> _domainEvents.Add(@event);

public void ClearDomainEvents()
=> _domainEvents.Clear();

// Called by infrastructure after successful persistence
public void BumpVersion()
=> Version++;
}

public sealed class DomainException : Exception
{
public DomainException(string message) : base(message) { }
}

public interface IDomainEvent
{
DateTimeOffset OccurredAt { get; }
}

Value Objects (AccountId, Money)

public readonly record struct AccountId(Guid Value)
{
public static AccountId New() => new(Guid.NewGuid());
public override string ToString() => Value.ToString("N");
}

public readonly record struct Money(decimal Amount)
{
public static Money Zero => new(0m);

public static Money operator +(Money a, Money b) => new(a.Amount + b.Amount);
public static Money operator -(Money a, Money b) => new(a.Amount - b.Amount);

public static bool operator <(Money a, Money b) => a.Amount < b.Amount;
public static bool operator >(Money a, Money b) => a.Amount > b.Amount;
public static bool operator <=(Money a, Money b) => a.Amount <= b.Amount;
public static bool operator >=(Money a, Money b) => a.Amount >= b.Amount;

public override string ToString() => Amount.ToString("0.00");
}

Domain Events

public sealed record FundsDeposited(AccountId AccountId, Money Amount, DateTimeOffset OccurredAt) : IDomainEvent;

public sealed record FundsWithdrawn(AccountId AccountId, Money Amount, DateTimeOffset OccurredAt) : IDomainEvent;

public sealed record AccountFrozen(AccountId AccountId, DateTimeOffset OccurredAt) : IDomainEvent;

Internal Entity of the Aggregate: Transaction

public sealed class Transaction : Entity<Guid>
{
public DateTimeOffset Timestamp { get; private set; }
public Money Amount { get; private set; }
public TransactionType Type { get; private set; }

private Transaction() { } // EF

private Transaction(Guid id, TransactionType type, Money amount, DateTimeOffset timestamp)
{
Id = id;
Type = type;
Amount = amount;
Timestamp = timestamp;
}

public static Transaction Deposit(Money amount, DateTimeOffset now)
=> new(Guid.NewGuid(), TransactionType.Deposit, amount, now);

public static Transaction Withdrawal(Money amount, DateTimeOffset now)
=> new(Guid.NewGuid(), TransactionType.Withdrawal, amount, now);
}

public enum TransactionType
{
Deposit = 1,
Withdrawal = 2
}

The Aggregate Root: Bank Account

Key points:

  • Method express intent
  • Invariants are guaranteed in the same place
  • The _transactions collection is private and exposed as read-only
  • External references (if any) would be by ID, never by object
public sealed class BankAccount : AggregateRoot<AccountId>
{
private readonly List<Transaction> _transactions = new();

public Money Balance { get; private set; }
public bool IsFrozen { get; private set; }

public IReadOnlyCollection<Transaction> Transactions
=> new ReadOnlyCollection<Transaction>(_transactions);

private BankAccount() { } // EF

public BankAccount(AccountId id)
{
Id = id;
Balance = Money.Zero;
IsFrozen = false;
}

public void Deposit(Money amount, DateTimeOffset now)
{
EnsureNotFrozen();
EnsurePositive(amount);

Balance += amount;
_transactions.Add(Transaction.Deposit(amount, now));

AddDomainEvent(new FundsDeposited(Id, amount, now));
}

public void Withdraw(Money amount, DateTimeOffset now)
{
EnsureNotFrozen();
EnsurePositive(amount);

if (Balance < amount)
throw new DomainException("Insufficient funds.");

Balance -= amount;
_transactions.Add(Transaction.Withdrawal(amount, now));

AddDomainEvent(new FundsWithdrawn(Id, amount, now));
}

public void Freeze(DateTimeOffset now)
{
if (IsFrozen) return;

IsFrozen = true;
AddDomainEvent(new AccountFrozen(Id, now));
}

private void EnsureNotFrozen()
{
if (IsFrozen)
throw new DomainException("Account is frozen.");
}

private static void EnsurePositive(Money amount)
{
if (amount <= Money.Zero)
throw new DomainException("Amount must be positive.");
}
}

Persistence (EF Core + SQL Server)

DbContext

  • Map AccountId and Money (conversions or owned types)
  • Map _transactions via backing field
  • Version as concurrency token (optimistic concurrency)
  • Ensure EF does not bypass encapsulation (field access)
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public sealed class BankingDbContext : DbContext
{
public DbSet<BankAccount> Accounts => Set<BankAccount>();

public BankingDbContext(DbContextOptions<BankingDbContext> options) : base(options) { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new BankAccountMap());
modelBuilder.ApplyConfiguration(new TransactionMap());
}
}

internal sealed class BankAccountMap : IEntityTypeConfiguration<BankAccount>
{
public void Configure(EntityTypeBuilder<BankAccount> b)
{
b.ToTable("BankAccounts");

// AccountId value object mapping
b.HasKey(x => x.Id);

b.Property(x => x.Id)
.HasConversion(
id => id.Value,
value => new AccountId(value))
.ValueGeneratedNever();

// Money mapping
b.Property(x => x.Balance)
.HasConversion(
m => m.Amount,
value => new Money(value))
.HasColumnType("decimal(18,2)")
.IsRequired();

b.Property(x => x.IsFrozen)
.IsRequired();

// Optimistic concurrency token
b.Property(x => x.Version)
.IsConcurrencyToken();

// Backing field mapping for Transactions collection
b.Metadata.FindNavigation(nameof(BankAccount.Transactions))!
.SetPropertyAccessMode(PropertyAccessMode.Field);

b.HasMany<Transaction>("_transactions")
.WithOne()
.HasForeignKey("AccountId") // shadow FK
.OnDelete(DeleteBehavior.Cascade);
}
}

internal sealed class TransactionMap : IEntityTypeConfiguration<Transaction>
{
public void Configure(EntityTypeBuilder<Transaction> b)
{
b.ToTable("AccountTransactions");

b.HasKey(x => x.Id);

b.Property(x => x.Amount)
.HasConversion(
m => m.Amount,
value => new Money(value))
.HasColumnType("decimal(18,2)")
.IsRequired();

b.Property(x => x.Timestamp).IsRequired();
b.Property(x => x.Type).IsRequired();
}
}

Repository (load/save aggregate as a unit)

The rule is: repository works with aggregate root.

using Microsoft.EntityFrameworkCore;

public interface IBankAccountRepository
{
Task<BankAccount?> GetAsync(AccountId id, CancellationToken ct);
void Add(BankAccount account);
}

public sealed class BankAccountRepository : IBankAccountRepository
{
private readonly BankingDbContext _db;

public BankAccountRepository(BankingDbContext db) => _db = db;

public async Task<BankAccount?> GetAsync(AccountId id, CancellationToken ct)
=> await _db.Accounts
.Include(a => a.Transactions) // materializa via navigation; backing field still protects writes
.SingleOrDefaultAsync(a => a.Id == id, ct);

public void Add(BankAccount account) => _db.Accounts.Add(account);
}

Note: Include(a => a.Transactions) uses the read-only property. The mapping configured Field access for writing, so you maintain encapsulation without becoming a hostage of EF.


SaveChanges + Domain Events dispatch

We need a strategy. Here's a simple and honest way:

  • Save the transaction
  • If saved, increment the version and clear domain events.
  • Publish events outside the domain (infrastructure).
  • If you need to guarantee delivery: this evolves into Outbox Pattern
public interface IDomainEventDispatcher
{
Task DispatchAsync(IEnumerable<IDomainEvent> events, CancellationToken ct);
}

public sealed class UnitOfWork
{
private readonly BankingDbContext _db;
private readonly IDomainEventDispatcher _dispatcher;

public UnitOfWork(BankingDbContext db, IDomainEventDispatcher dispatcher)
{
_db = db;
_dispatcher = dispatcher;
}

public async Task CommitAsync(CancellationToken ct)
{
// Gather events before SaveChanges (they exist in memory)
var aggregates = _db.ChangeTracker
.Entries()
.Where(e => e.Entity is AggregateRoot<AccountId> || e.Entity.GetType().BaseType?.IsGenericType == true)
.Select(e => e.Entity)
.OfType<dynamic>() // keep sample small
.ToList();

var events = aggregates
.SelectMany(a => (IEnumerable<IDomainEvent>)a.DomainEvents)
.ToList();

// Save with optimistic concurrency
await _db.SaveChangesAsync(ct);

// Post-persistence bookkeeping
foreach (var a in aggregates)
{
a.BumpVersion();
a.ClearDomainEvents();
}

await _dispatcher.DispatchAsync(events, ct);
}
}

Example usage (application layer)

public sealed class WithdrawFundsHandler
{
private readonly IBankAccountRepository _repo;
private readonly UnitOfWork _uow;
private readonly TimeProvider _clock;

public WithdrawFundsHandler(IBankAccountRepository repo, UnitOfWork uow, TimeProvider clock)
{
_repo = repo;
_uow = uow;
_clock = clock;
}

public async Task Handle(AccountId accountId, Money amount, CancellationToken ct)
{
var account = await _repo.GetAsync(accountId, ct)
?? throw new DomainException("Account not found.");

account.Withdraw(amount, _clock.GetUtcNow());

await _uow.CommitAsync(ct);
}
}

Mapping to the concept (updated)

  • BankAccount is the Entity and the Aggregate Root
  • Transaction is an Entity inside the aggregate, not a root
  • Invariants are enforced only in the root
  • Persistence treats the aggregate as a unit:
    • one load boundary (GetAsync)
    • one save boundary (CommitAsync)
    • concurrency detected via Version token
  • Domain events capture “what happened” without coupling the domain to infrastructure

6. Design trade-offs

ChoiceGainCost
Small aggregatesScalability, concurrencyMore async coordination
Large aggregatesStrong consistencyWrite contention
Synchronous invariantsCorrectnessLatency
Eventual consistencyThroughputComplexity
ORM mappingProductivityRisk of leakage

Implicitly accepted:

  • distributed transactions are avoided
  • not all reads are strongly consistent
  • complexity moves into the domain model

7. Common mistakes and misconceptions

"Aggregates should be large to match the domain"

Why it happends:

  • conceptual modeling without concurrency thinking

Problem:

  • write contention, deadlocks

Avoidance:

  • size aggregates by consistency needs, not nouns

"Repositories can update child entities directly"

Why it happends:

  • ORM convenience

Problem:

  • invariants bypassed

Avoidance:

  • repository only loads and saves aggregate roots

"Validation in services is enough"

Why it happends:

  • anemic models

Problem:

  • rules skipped, duplicated, or reordered

Avoidance:

  • invariants live inside the aggregate

"Aggregates should reference other aggregates"

Why it happends:

  • object graph thinking

Problem:

  • hidden coupling

Avoidance:

  • reference by ID only

8. Operational and production considerations

Things to monitor:

  • optimistic concurrency failures
  • retry rates
  • aggregate load times
  • deadlocks and transaction duration

What degrades first:

  • write throughput
  • tail latency under contention

What becomes expensive:

  • large aggregates with frequent writes
  • chatty domain events
  • over-eager eager loading

Operational risks:

  • incorrect boundaries locking scaling paths
  • silent invariant violations via migrations
  • ORM misconfiguration bypassing encapsulation

Observability signals:

  • version conflicts
  • domain exception rates
  • event publication lag

9. When NOT to use this

Do not use aggregates when:

  • data is independent
  • rules are trivial
  • writes are rare and non-conflicting
  • the system is analytics-only
  • eventual consistency is acceptable everywhere

Using aggregates here is harmful:

  • unncessary complexity
  • reduced performance
  • false sense of safety

10. Key takeaways

  • Aggregates exist to protect business invariants, not data structure
  • Size aggregates by consistency, not by entity count
  • All state changes go through the aggregate root
  • Transactions enforce atomicity, aggregates enforce correctness
  • Cross-aggregate consistency is always eventual
  • ORM convenience is a liability if not controlled
  • If it must be correct now, it belongs inside the aggregate

11. High-Level Overview

Visual representation of aggregate boundaries, highlighting enforced invariants, transactional consistency within the root, and eventual consistency across integration boundaries.

Scroll to zoom • Drag to pan
DDD Aggregates — Consistency Boundary (C#/.NET)DDD Aggregates — Consistency Boundary (C#/.NET)Application LayerDomain LayerInfrastructure Layer«usecase»CommandHandlerHandle(accountId, amount)«entity»EntityBaseIdEqualsByIdentity()«aggregate_root»AggregateRootBaseVersion : intDomainEvents : IReadOnlyCollectionAddDomainEvent()ClearDomainEvents()«aggregate_root»BankAccountBalance : MoneyIsFrozen : bool_transactions : List<Transaction>Deposit(amount, now)Withdraw(amount, now)Freeze(now)«entity»TransactionId : GuidType : TransactionTypeAmount : MoneyTimestamp : DateTimeOffsetIDomainEvent«domain_event»FundsDeposited«domain_event»FundsWithdrawn«domain_event»AccountFrozenAGGREGATE BOUNDARY- Single entry point for mutations- Invariants enforced synchronously- Internal entities controlled by root- External references by ID onlySYNCHRONOUS INVARIANTS- No overdraft- No operations when frozen- Atomic update of Balance + Transactions«repository»RepositoryGet(accountId)Add(account)«uow»UnitOfWorkCommitAsync()1) Collect domain events2) SaveChanges (transaction)3) Optimistic concurrency (Version)4) Clear events + bump version5) Dispatch events«dispatcher»IDomainEventDispatcherDispatchAsync(events)«database»SqlServerBankAccountsAccountTransactionsVersion (concurrency token)«queue»MessageBrokerAzure Service Bus / KafkaOPERATIONAL REALITY- Dispatch can fail independently- Guaranteed delivery requires Outboxowns(consistency boundary)0..*Load / Save(root only)SaveChanges(transaction)Dispatch(events)Publish(async boundary)Load aggregateExecute behaviorCommitAsync()plantuml-src jLVTRjis5BxFKn3fnUosJfl-OAsH6P1bsHNcM86ij0qm8A0bsYOiamf9sNMt0dk8FU6UP8UKPDEAdQGN2r0q8Z-Uyvs_ZuoYAYMer-iXPpOJP8OLaUZVl_z1BcUIIaLOl45TdhC4YmrgkCzUdOsyg7bo8XUKPLZW9P9pdF0rPJCqnQaariu4nukPq8TTddA1dlNDZuKmcp4NZ0Xq_iRQS8JWwsXEumKZKgBpCtipfOoeJKOG5shEhOqC7E8P6QiD12L8h32RfJQZX4nndge-Psg4bmJSK9oUdFo2VZls0tjeC0owpX15tjS8kS4m21yySD1RcO3N_VDVtpYFElI6tgKtYj0ukXbwuoTwYrCCcVhp1A7klRn_w3jzL-zmaH1HhhhTtdll75Rl4_nXsQLC4J75CVaVR9S5EsBvlF--_x5k-UCxvsttmwEM6LV7p1PD-3Dc7wkNStKrz5qdyeFHWz0gt5Cdor8QOqKvGqEy8U8KOQdD6Cf59Ls-N6AMV89_AOp7nKKkIOmbwNGC2A4NgDXhu3Y6-9IVl4HugNzhmlwF9_3k1PUE_qJAFRx4r6PR70OkMyOUKrHjki0Hs1BpOP7ra_8Nxou7eUXk_AJ0D0gwMpDR9GevLwKrNAtT2bYqh7ucGkeijdMVbci5LMy5vYLi-271IS3I3PGr1L40U0bqaiJ26XvwsKq95hQHEiCkPWkdIFfnRYs0fPZ1tBJH9MTaKwxwiY_uTw8fJpXFozLR9J2JsB3Jf8SWlXVHRgrJnKOoBgbg58L-2Ns-hcX_eMgU2Bm-jDSNX7mdZNB52iLoSRXem6MGqwfsaPRTjdrAhrJfD7xtmeKJT4camiiCrdkG9FqTJAUIg8h8Jc1yA-axYdsONLd6JH9WcHZKBT6mJcSVLoM17SULbIkhS0y6kFiTs6hzrR7ws-ZAhWVCxhr39pNKyrOBdRu-Etj-kfVrDk9h9lzWZTYwSYVbbTlKHce9E3i3RtkfGlNO3q7smxwF08WHJK5dSuNurEPkQkaC1g4tS28FTOFhKSy9RyhU7LDzpI8m0c-4Z4CzqHJqSvahet2oQdosmd3Vwg4a300nn28tB9uBpdWkq-s0w7vWE4Mc6oayI20pIkZXJT1aW_IKbL3lMmaLP4e4P0x0WF1xY3DZZx34HBMDRiALuij3uOrlHkwdC1W5rsFaZpuxeU-Ced7fQCGHNn41sPqgQoaZeWWHhUUGrAd9RRdlW1yQepnBe2SAXuKSlB3BBssIZoksF-g7pZWAhzteElIUfjm-cmeibSXZbGjYARWnPWjuMFGOXp9UN8ZjXwK70w9srqrp9wFLOarThXbLmVGB5mjzV_6rPKbVRrGvkW5AuMsrp7_dJLGADIec4vd9BBheJHEDyOguStZPGQqRbceMHjusKP0fkeH1WkH3uyIv46Qa6kLLKU3UDGkPBuq3eKaE6hKgC0RolebwL6POnVCTXGSKgmARophPVbWnLuWYwi9cBMFZktHCnAgmWHMUs3U-rR95EigFMZ-XxQtOi1EW-8AmchjBU5r3atG5x6cNTpd9T_wSxxfXD1yAiNPpYLwXt_5qWIiZLiEqMXsBF5nW72S0rzK2RTCpQsPI-xUwedxAAYwSgbMtTkv8xjlRD5iPFeBLPlPJq4PN-IIbSWxkiIxJlXJND6Jlh4bPSEM5vY48Vp24dZFqeqebjxqKOr0AJ5DeeuHaCFp0QIj-WnoUhz1U87K9IIba1GIFtELKGBi7kPhmRp-d6DvNpxtMX51_67nv45rxSUhaM0KkIxfzxHo2szTT6zIPnBdE5vdZ5UNYq0chfkrzJVWF?>DDD Aggregates — Consistency Boundary (C#/.NET)DDD Aggregates — Consistency Boundary (C#/.NET)Application LayerDomain LayerInfrastructure Layer«usecase»CommandHandlerHandle(accountId, amount)«entity»EntityBaseIdEqualsByIdentity()«aggregate_root»AggregateRootBaseVersion : intDomainEvents : IReadOnlyCollectionAddDomainEvent()ClearDomainEvents()«aggregate_root»BankAccountBalance : MoneyIsFrozen : bool_transactions : List<Transaction>Deposit(amount, now)Withdraw(amount, now)Freeze(now)«entity»TransactionId : GuidType : TransactionTypeAmount : MoneyTimestamp : DateTimeOffsetIDomainEvent«domain_event»FundsDeposited«domain_event»FundsWithdrawn«domain_event»AccountFrozenAGGREGATE BOUNDARY- Single entry point for mutations- Invariants enforced synchronously- Internal entities controlled by root- External references by ID onlySYNCHRONOUS INVARIANTS- No overdraft- No operations when frozen- Atomic update of Balance + Transactions«repository»RepositoryGet(accountId)Add(account)«uow»UnitOfWorkCommitAsync()1) Collect domain events2) SaveChanges (transaction)3) Optimistic concurrency (Version)4) Clear events + bump version5) Dispatch events«dispatcher»IDomainEventDispatcherDispatchAsync(events)«database»SqlServerBankAccountsAccountTransactionsVersion (concurrency token)«queue»MessageBrokerAzure Service Bus / KafkaOPERATIONAL REALITY- Dispatch can fail independently- Guaranteed delivery requires Outboxowns(consistency boundary)0..*Load / Save(root only)SaveChanges(transaction)Dispatch(events)Publish(async boundary)Load aggregateExecute behaviorCommitAsync()plantuml-src jLVTZjeu5BxdAQpfnK9RPeTfQLKqYnGWq6WPCWgP_aWhLIPnm2BOZEr0wMwbVOXzmdsIFNOIC1bcEdlHaQgPs9_F-SxVPrTHbH8q60oGCvyBCiUAIFJlt_-WFcUIIaLOj4Czdh4OYnsgzv_zUZvnmqQj9fUKhR70AoGNEEPRokOemQaariuCHykvq8VxFEK2AO6PX2t2b8KoW8WBHWHgNLeRZX1y6ovej6H4IjGwjpTJoeZQhGdYGYsujR46ftXEfce7WGaIASpcgSqg9WdEKZNaJ4tmYe0RYjDQxHVqsqC_i8T6Ox_dZ57eVWfHtn_xmQC7JdhBa_3CRRjltTuF7RfZzzgTX6WQVXwxqoTwYr8CcVgpXb2laljd5xtMvUM5tk4Y9g9OVVNgTQlTXbMBuB0rR0_VrRvRTYbJH2Gu8Z_1Tb6mds2PSVMGsSkBDwstLREltxRRRzuTcosgzVs7zN9kRiPUtmazV_8ej0ptp5clKnfXHJb3OxmZuWnXgSqOodab-tormYn-3_zI68-hgqoI24lIxHeGGYzGlbV7KGJnAIz-YV1A_zM0_UzFu3tmhntlYPG7V8MfpJO_35psZ5scgDhrW2Em9UR38kl5nH_kNGPYqTjvSOwfvtJtPlPg572k2ckuNFiYOD6o-e48gRFOqNrQhELKtGsObx3X1GJ7FajtKDOKH07W1T29OmjhUEZbVagmi8rK6VOmMpfvqX_crWHOYXdCJGTTSqPsnQedXu9_8vhoZFEqMFrY118RTfhq60JuAZoiTSlOo9fBgkfveLz2dszBsX-fMiG2Rq_j3GKXtqYzMB52iLoSBXfm6MMqh5sePRTZdz8hPJgDtwDmuGHT4QdmQWth0qYI_lQJH19L4Za8Z6Sb_K1n2BChYxX932nZW_f2DApRFSQLIM0FuuhA5LMu1mFS_GviDl_gMlrjTENG0sRttg5Q1VMysKHd5-Vdpy-Eijv1VCla7wmUMTVkhBXs6zf89G7dv-3jA5Me6lifo77Ozn40CQ8fw7oX44zixgQMpcWKk2CdT57FlvqCdE1pqRjJggzP14RWdR3cK4-KW7wkCcKKJfQDppOOxbizb8G1889Ov8v52y4PpsIw7n3T3modo7GZXKS9P4O9FRmncksGdh82wduje88a1DuM4O01uGqGPyOUOR69QX_TZ2l5LwV2ctwUzDy7_iI_dI9lyi49F6SIJWj74uxuXWZ8RgAifJKHUOXeku2a9YQtnRu3VcY4idKCFP4xpENWXLr-QPFyiM9xas7WJCFWjX_U1kxJbDjZYS1IYInIcI2MWXjZje07UOznAEFLbTX_M7em8kfmtJGECbekLjJbbb7b9n-vMEhxYsyjI_fweyhH3L08RxDfVhKQg11gb4ycCfEPTz5b0qtnXlGNyBA3MjSjrSoDl6eWVwte2WO9aW-D4sL2c96g5rT5ZdlTo6M-C0w4PXber2R764YxWGOKtgmgMXmeFA9O9TXSrl7-mugvHEHHvpOh6PlUfLCYDhaDhF3Cll6jbitJKNnK-WdjRyMwdG35buHLt5t3wngQf2TWJxkyoqXsyETyqmsZ-L28jPT9z2lw7ITBN1gn6gRPx5hauGBZE0QuhXPecvvPCvFQlzKLrLDMSU5KfRgLSm_alhDFivNX1x3Qp74AEkWccwLKBi0zrcKwbkAAXXoTDIdpRzp0F0JXFmo1wuozi5J9VIz569G2qnJQA2Ph67xWj1U_KGRFLsWlaBgOf1Io0e97xZ8geDtzJCtur_-d6Eudjt-h2Q7XsF_uABhoujJ9iGfSb7J_sZa5jw-x3gWpYJATBxB06yh5gHDMJJl7cl0V?>