Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/perf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
config:
- branch: 'v2.10'
- branch: 'v2.12'
- branch: 'latest'
- branch: 'main'
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
fail-fast: false
matrix:
config:
- branch: 'v2.10'
- branch: 'v2.11'
- branch: 'v2.12'
- branch: 'latest'
- branch: 'main'
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_linux_core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
fail-fast: false
matrix:
config:
- branch: 'v2.10'
- branch: 'v2.11'
- branch: 'v2.12'
- branch: 'latest'
- branch: 'main'
runs-on: ubuntu-latest
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/test_natsiodocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Test Examples NatsIODocs

on:
pull_request: {}
push:
branches:
- main

permissions:
contents: read

jobs:
build:
name: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
NUGET_XMLDOC_MODE: skip
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'
cache: true

- name: Install nats-server
run: go install github.com/nats-io/nats-server/v2@latest

- name: Check nats-server
run: nats-server -v

- name: Test
run: dotnet test examples/Example.NatsIODocs/Example.NatsIODocs.csproj -c Release
2 changes: 1 addition & 1 deletion .github/workflows/test_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
fail-fast: false
matrix:
config:
- branch: 'v2.10'
- branch: 'v2.11'
- branch: 'v2.12'
- branch: 'latest'
- branch: 'main'
runs-on: windows-latest
Expand Down
1 change: 1 addition & 0 deletions NATS.Net.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Project Path="examples/Example.JetStream.PullConsumer/Example.JetStream.PullConsumer.csproj" />
<Project Path="examples/Example.KeyValueStore.Watcher/Example.KeyValueStore.Watcher.csproj" />
<Project Path="examples/Example.NativeAot/Example.NativeAot.csproj" />
<Project Path="examples/Example.NatsIODocs/Example.NatsIODocs.csproj" />
<Project Path="examples/Example.ObjectStore/Example.ObjectStore.csproj" />
<Project Path="examples/Example.OpenTelemetry/Example.OpenTelemetry.csproj" />
<Project Path="examples/Example.ProtoBufMessages/Example.ProtoBufMessages.csproj" />
Expand Down
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,74 @@ Head over to [NATS documentation](https://docs.nats.io/nats-concepts/overview) f
- **NATS.Client.Hosting**: DI integration for AOT-compatible and minimal-dependency scenarios
- **NATS.Extensions.Microsoft.DependencyInjection**: DI integration with ad-hoc JSON serialization enabled by default

## Client and Orbit

NATS client functionality is split across two layers: the **core client**
(`NATS.Net`, this repo) and **[Orbit](https://github.com/synadia-io/orbit.net)**,
a separate set of packages with higher-level utilities.

The split exists so the core can stay small, stable, and consistent across
NATS clients in every language, while Orbit can iterate quickly on
opinionated abstractions without dragging the core API along for the ride.

### Core client (`NATS.Net`)

- Direct API over Core NATS and JetStream as exposed by `nats-server`.
- Lightweight, unopinionated, performance-oriented.
- API surface kept in **parity** with other official NATS clients
(Rust, Go, Java, JS, Python, C). A feature shipped here should look
the same shape everywhere.
- Stable, conservative versioning. Breaking changes are rare and deliberate.

### Orbit (`orbit.net`)

- Higher-level, opinionated abstractions built **on top of** the core client.
- Per-package versioning, so an experimental utility can iterate
without bumping every other piece.
- Free to be language-specific: a .NET-idiomatic API does not need to match
the equivalent in other languages.
- May lag, omit, or extend cross-client parity items.

### What goes where?

| Concern | Core (`NATS.Net`) | Orbit |
|----------------------------------------------------|:-------------------:|:-----:|
| Connect, publish, subscribe, request/reply | ✅ | |
| JetStream publish, consumers, streams, KV, OS | ✅ | |
| Service API (request/reply micro-services) | ✅ | |
| Wire-protocol coverage, auth, TLS, reconnection | ✅ | |
| Cross-client parity, conservative semver | ✅ | |
| Opinionated helpers / sugar over core APIs | | ✅ |
| New experimental patterns (e.g. partitioned groups)| | ✅ |
| KV codecs, distributed counters, NATS contexts | | ✅ |
| .NET-idiomatic abstractions with no parity mandate | | ✅ |
| Per-utility versioning, faster API churn allowed | | ✅ |

> **Rule of thumb:** if it is a thin mapping of something `nats-server`
> already speaks and every official client must expose it, it belongs in
> core. If it is a pattern, helper, or abstraction layered on top, it
> belongs in Orbit.

### Layering

```text
┌──────────────────────────────────────────────────────┐
│ Application code │
└──────────────┬───────────────────────────┬───────────┘
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Orbit packages │ uses │ NATS.Net (core) │
│ (opinionated, │──────▶│ (parity, stable, │
│ per-pkg semver) │ │ protocol-level) │
└───────────────────┘ └─────────┬─────────┘
┌─────────────┐
│ nats-server │
└─────────────┘
```

## Contributing

You are welcome to contribute to this project. Here are some steps to get you started:
Expand Down
23 changes: 23 additions & 0 deletions examples/Example.NatsIODocs/BasicsPublish.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using NATS.Net;

namespace Example.NatsIODocs;

[Collection("nats-server")]
public class BasicsPublish(NatsServerFixture fixture, ITestOutputHelper output)
{
[Fact]
public async Task RunAsync()
{
await using var client = new NatsClient(fixture.Server.Url);

var sub = await client.Connection.SubscribeCoreAsync<string>("weather.updates");

// NATS-DOC-START
// Publish a message to the subject "weather.updates"
await client.PublishAsync("weather.updates", "Temperature: 72F");

// NATS-DOC-END
var msg = await sub.Msgs.ReadAsync();
output.WriteLine($"Received: {msg.Data}");
}
}
27 changes: 27 additions & 0 deletions examples/Example.NatsIODocs/BasicsSubscribe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NATS.Net;

namespace Example.NatsIODocs;

[Collection("nats-server")]
public class BasicsSubscribe(NatsServerFixture fixture, ITestOutputHelper output)
{
[Fact]
public async Task RunAsync()
{
await using var client = new NatsClient(fixture.Server.Url);
_ = Task.Run(async () =>
{
// NATS-DOC-START
// Subscribe to 'weather.updates' and process messages
await foreach (var msg in client.SubscribeAsync<string>("weather.updates"))
{
output.WriteLine($"Received: {msg.Data}");
}

// NATS-DOC-END
});

await Task.Delay(1000);
await client.PublishAsync("weather.updates", "sunny");
}
}
29 changes: 29 additions & 0 deletions examples/Example.NatsIODocs/Example.NatsIODocs.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Synadia.Orbit.Testing.NatsServerProcessManager" Version="1.0.0-preview.2" />
<PackageReference Include="xunit.v3" Version="1.0.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\NATS.Net\NATS.Net.csproj" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions examples/Example.NatsIODocs/GettingStartedPublish.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NATS.Net;

namespace Example.NatsIODocs;

public class GettingStartedPublish
{
[Fact(Skip = "Targets demo.nats.io; not run in CI.")]
public async Task RunAsync()
{
// NATS-DOC-START
await using var client = new NatsClient("demo.nats.io");

// Publish a message to the subject "hello"
await client.PublishAsync("hello", "Hello NATS!");

// NATS-DOC-END
}
}
21 changes: 21 additions & 0 deletions examples/Example.NatsIODocs/GettingStartedSubscribe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using NATS.Net;

namespace Example.NatsIODocs;

public class GettingStartedSubscribe(ITestOutputHelper output)
{
[Fact(Skip = "Targets demo.nats.io and blocks waiting for messages; not run in CI.")]
public async Task RunAsync()
{
// NATS-DOC-START
await using var client = new NatsClient("demo.nats.io");

// Subscribe to 'hello' and process messages
await foreach (var msg in client.SubscribeAsync<string>("hello"))
{
output.WriteLine($"Received: {msg.Data}");
}

// NATS-DOC-END
}
}
20 changes: 20 additions & 0 deletions examples/Example.NatsIODocs/NatsServerFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Synadia.Orbit.Testing.NatsServerProcessManager;

namespace Example.NatsIODocs;

public sealed class NatsServerFixture : IDisposable
{
public NatsServerFixture()
{
Server = NatsServerProcess.Start();
}

public NatsServerProcess Server { get; }

public void Dispose() => Server.Dispose();
}

[CollectionDefinition("nats-server")]
public class NatsServerCollection : ICollectionFixture<NatsServerFixture>
{
}
51 changes: 51 additions & 0 deletions examples/Example.NatsIODocs/QueueGroupsBasic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using NATS.Net;

namespace Example.NatsIODocs;

[Collection("nats-server")]
public class QueueGroupsBasic(NatsServerFixture fixture, ITestOutputHelper output)
{
[Fact]
public async Task RunAsync()
{
await using var client = new NatsClient(fixture.Server.Url);

// NATS-DOC-START
// Create three workers in the same queue group
_ = Task.Run(async () =>
{
await foreach (var msg in client.SubscribeAsync<string>("orders.new", queueGroup: "new-orders-queue"))
{
output.WriteLine($"Worker A processed: {msg.Data}");
}
});

_ = Task.Run(async () =>
{
await foreach (var msg in client.SubscribeAsync<string>("orders.new", queueGroup: "new-orders-queue"))
{
output.WriteLine($"Worker B processed: {msg.Data}");
}
});

_ = Task.Run(async () =>
{
await foreach (var msg in client.SubscribeAsync<string>("orders.new", queueGroup: "new-orders-queue"))
{
output.WriteLine($"Worker C processed: {msg.Data}");
}
});

// Let subscription tasks start
await Task.Delay(1000);

// Publish messages once all subscriptions are set up
for (var i = 1; i <= 10; i++)
{
await client.PublishAsync("orders.new", $"Order Number: {i}");
}

// NATS-DOC-END
await Task.Delay(1000);
}
}
Loading
Loading