---
title: Migrate from string routing
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/event-sources/migrating.mdx
description: Move from eventSourceType and eventStreamType strings and the matching attributes to an event source definition without orphaning stored events.
---

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


Use this page when your code appends with `eventSourceType:`, `eventStreamType:` and `eventStreamId:` strings and observes with `[EventSourceType]` and `[EventStreamType]`, and you want one declared place for those names.

## What changes and what does not

The stored events do not change. A definition writes the same two values the strings did: the event source type and the event stream type. If the definition's names equal the strings you used, events you wrote before the migration still match the new filters, and new events match the old ones.

What the definition adds is the declaration, the checks on both sides, the stream-level concurrency dimensions and `EventContext.EventSource`, which is set only on events appended through a definition. Older events have it empty.

## Steps

1. **Collect the strings in use.** Search for `eventSourceType:`, `eventStreamType:`, `[EventSourceType(` and `[EventStreamType(`. Each distinct event source type becomes a definition. Each distinct stream type that goes with it becomes a stream.
2. **Declare the definitions with the existing strings as names.** Do not tidy the casing in this step. `order` and `Order` are different event source types.
3. **Switch the appends** to `Append<TSource>` with `eventStream:` and `eventStreamId:`. Keep the stream id values as they were.
4. **Switch the observers** to `[FromEventSource<TSource>("stream")]`.
5. **Build.** The analyzer reports a stream the definition does not declare ([CHR0055](/chronicle/code-analysis/chr0055/)) and a leftover string attribute that contradicts the typed filter ([CHR0056](/chronicle/code-analysis/chr0056/)). Remove the leftover attributes.
6. **Choose the concurrency dimensions** once the routing is typed. Until you declare any, the appends keep the guard they had.

## Before and after

Before, the routing is repeated at every call site and in every observer:

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

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.Reactors;

[EventType]
public record PaymentReceived(decimal Amount);

public class PaymentWriter(IEventLog eventLog)
{
    // The routing values are strings that every caller must repeat exactly.
    public Task<AppendResult> Receive(EventSourceId orderId, decimal amount, string period) =>
        eventLog.Append(
            orderId,
            new PaymentReceived(amount),
            eventStreamType: "payments",
            eventStreamId: period,
            eventSourceType: "order");
}

[EventSourceType("order")]
[EventStreamType("payments")]
public class PaymentReactor : IReactor
{
    public Task Received(PaymentReceived @event, EventContext context) => Task.CompletedTask;
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/migrating/before.md)

</TabItem>
</Tabs>

After, it is declared once:

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

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.EventSources;
using Cratis.Chronicle.Reactors;

// The names are the strings that are already stored on existing events, so those events still match.
[EventSource("order")]
[EventStream("payments")]
public class OrderEventSource : IEventSource;

public class TypedPaymentWriter(IEventLog eventLog)
{
    public Task<AppendResult> Receive(EventSourceId orderId, decimal amount, string period) =>
        eventLog.Append<OrderEventSource>(
            orderId,
            new PaymentReceived(amount),
            eventStream: "payments",
            eventStreamId: period);
}

[FromEventSource<OrderEventSource>("payments")]
public class TypedPaymentReactor : IReactor
{
    public Task Received(PaymentReceived @event, EventContext context) => Task.CompletedTask;
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/event-sources/migrating/after.md)

</TabItem>
</Tabs>

The definition names are `order` and `payments`, the strings from before, so the existing events still match.

## Do it in stages

You can migrate one event source at a time. Appends and observers that still use strings keep working, because the kernel accepts appends that do not name a definition exactly as it always did, even when their event source type happens to equal a registered name. Typed and string forms interoperate as long as the values are equal: a `[FromEventSource<T>]` observer receives events written with the matching strings, and a string observer receives events written through the definition.

## When the strings and the names must differ

If you want a new, cleaner name, treat it as a rename, not a migration. Events already stored keep the old event source type, and observers on the new name will not see them. See [Questions and pitfalls](/chronicle/event-sources/faq/#what-happens-when-i-rename-an-event-source-or-a-stream).

## Next steps

- [Define an event source](/chronicle/event-sources/defining/)
- [Filter observers](/chronicle/event-sources/filtering/)
- [Filter by event source type](/chronicle/events/filtering/by-event-source-type/)
- [Dynamic Consistency Boundary in Chronicle](/chronicle/dynamic-consistency-boundary/chronicle/): what a typed definition does and does not change about consistency.
