---
title: 'CHR0032: Reactor must not access storage directly'
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

## Rule Description

A class implementing `IReactor` injects a storage primitive such as `IMongoCollection<T>` through its constructor. Injecting a sink couples the reactor to the persistence layer and bypasses Chronicle's read-model abstraction.

Read keyed state through an injected read model parameter or `IReadModels.GetInstanceById` instead.

## Severity

Warning

## Example

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Reactors;
using MongoDB.Driver;

// Warning CHR0032: Reactor injects storage primitive 'orders' directly; this couples the
// reactor to a sink and bypasses Chronicle's read-model abstraction. Read keyed state
// through an injected read model parameter or IReadModels.GetInstanceById instead.
public class Chr0032OrderProcessor(IMongoCollection<Chr0032Order> orders) : IReactor
{
    public async Task OrderPlaced(Chr0032OrderPlaced @event, EventContext context) =>
        await orders.Find(order => order.OrderNumber == @event.OrderNumber).FirstOrDefaultAsync();
}

[EventType]
public record Chr0032OrderPlaced(string OrderNumber);

public record Chr0032Order(string OrderNumber);
```

</TabItem>
</Tabs>

## Why This Rule Exists

A reactor should express intent, not reach into the persistence layer. Injecting `IMongoCollection<T>` ties the reactor to a specific sink (the MongoDB collection backing a read model) and lets it read or write documents directly — outside the projection pipeline that owns that state. The reactor then depends on storage-layer details and can observe or corrupt read-model state that Chronicle is responsible for building.

When a reactor needs current state, it reads it through the read-model abstraction: declare a read-model parameter on the handler (Chronicle materializes it strongly consistent for the event being handled) or call `IReadModels.GetInstanceById`. The read model stays the single source of derived state, and the reactor stays decoupled from where that state is persisted.

## Related Rules

- [CHR0031: Reactor must not have mutable state](/chronicle/code-analysis/chr0031/) — reactors must be stateless.
- **CHR0022** — a reactor handler returning side-effect events must be marked `[OnceOnly]`.
