Skip to content

Commit 9100686

Browse files
committed
Add community files and update docs for v0.1.0-alpha.1
New files: - DECISION_LOG.md: 10 design decisions with rationale and SDK comparison - CONTRIBUTING.md: prerequisites, build/test, PR process, coding standards - .github/ISSUE_TEMPLATE/: bug report and feature request templates - .github/PULL_REQUEST_TEMPLATE.md: PR checklist - .github/CODEOWNERS: @BeshoyHindy - .github/dependabot.yml: weekly NuGet + Actions updates - .github/workflows/codeql.yml: CodeQL security analysis - .gitattributes: line endings, diff drivers, binary markers Updated: - README.md: mock context features, interfaces section - CHANGELOG.md: restructuring notes (interfaces, testing, generator, RunOptions)
1 parent 0979d67 commit 9100686

File tree

11 files changed

+377
-1
lines changed

11 files changed

+377
-1
lines changed

.gitattributes

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
* text=auto
2+
3+
*.cs diff=csharp
4+
*.csproj diff=xml
5+
*.sln text eol=lf
6+
*.slnx text eol=lf
7+
*.md text eol=lf
8+
*.yml text eol=lf
9+
*.yaml text eol=lf
10+
*.json text eol=lf
11+
*.xml text eol=lf
12+
*.props text eol=lf
13+
*.targets text eol=lf
14+
15+
*.png binary
16+
*.dll binary
17+
*.nupkg binary
18+
*.snupkg binary

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @BeshoyHindy
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
name: Bug Report
3+
about: Report a bug in the Restate .NET SDK
4+
title: ''
5+
labels: bug
6+
assignees: ''
7+
---
8+
9+
**Environment**
10+
- .NET SDK version:
11+
- Restate server version:
12+
- OS:
13+
14+
**Describe the bug**
15+
A clear description of the unexpected behavior.
16+
17+
**Steps to reproduce**
18+
1.
19+
2.
20+
3.
21+
22+
**Expected behavior**
23+
What you expected to happen.
24+
25+
**Actual behavior**
26+
What actually happened. Include error messages or stack traces if applicable.
27+
28+
**Code sample**
29+
```csharp
30+
// Minimal code to reproduce
31+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: Feature Request
3+
about: Suggest a new feature or improvement
4+
title: ''
5+
labels: enhancement
6+
assignees: ''
7+
---
8+
9+
**Use case**
10+
Describe the problem or scenario this feature would address.
11+
12+
**Proposed API**
13+
```csharp
14+
// What the API could look like
15+
```
16+
17+
**Alternatives considered**
18+
Any alternative approaches you've considered.
19+
20+
**Additional context**
21+
Links to related issues, SDK comparisons, or documentation.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Summary
2+
3+
<!-- Brief description of the change -->
4+
5+
## Changes
6+
7+
-
8+
9+
## Test plan
10+
11+
- [ ] Tests added/updated
12+
- [ ] `dotnet build` succeeds with 0 warnings
13+
- [ ] `dotnet test` passes
14+
- [ ] `dotnet format --verify-no-changes` passes

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: nuget
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
open-pull-requests-limit: 10
9+
10+
- package-ecosystem: github-actions
11+
directory: /
12+
schedule:
13+
interval: weekly
14+
day: monday
15+
open-pull-requests-limit: 5

.github/workflows/codeql.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
- cron: '0 6 * * 1'
10+
11+
jobs:
12+
analyze:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
security-events: write
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: github/codeql-action/init@v3
19+
with:
20+
languages: csharp
21+
- uses: actions/setup-dotnet@v4
22+
with:
23+
dotnet-version: '10.0.x'
24+
- run: dotnet build --configuration Release
25+
- uses: github/codeql-action/analyze@v3

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Core SDK (`Restate.Sdk`) with full context hierarchy: `Context`, `ObjectContext`, `SharedObjectContext`, `WorkflowContext`, `SharedWorkflowContext`
13+
- Context interfaces: `IContext`, `ISharedObjectContext`, `IObjectContext`, `ISharedWorkflowContext`, `IWorkflowContext` for utility methods, type constraints, and documentation
1314
- Service types: `[Service]`, `[VirtualObject]`, `[Workflow]`
14-
- Handler attributes: `[Handler]`, `[SharedHandler]` with handler-level configuration
15+
- Handler attributes: `[Handler]`, `[SharedHandler]` with handler-level configuration via `HandlerAttributeBase`
1516
- Durable execution primitives: `Run`, `RunAsync`, `Sleep`, `After`
1617
- Service-to-service communication: `Call`, `CallFuture`, `Send` with typed clients
1718
- State management: `Get`, `Set`, `Clear`, `ClearAll`, `StateKeys` via `StateKey<T>`
@@ -23,16 +24,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2324
- Replay-aware logging: `DurableConsole` with `ReplayAwareInterpolatedStringHandler`
2425
- Ingress client (`RestateClient`) for external service invocation
2526
- Source generator (`Restate.Sdk.Generators`) for typed client and service definition generation
27+
- Source generator diagnostics: RESTATE001-009, including compile-time TimeSpan validation (RESTATE009)
28+
- Source generator hardening: `#pragma warning disable` on generated code, null checks in deserializers
2629
- Testing package (`Restate.Sdk.Testing`) with `MockContext`, `MockObjectContext`, `MockWorkflowContext`, `MockSharedObjectContext`, `MockSharedWorkflowContext`
30+
- Testing features: deterministic `CurrentTime`, `SetupCallFailure()`, `RegisterClient<T>()`, call/send/sleep recording
2731
- AWS Lambda adapter (`Restate.Sdk.Lambda`) with `RestateLambdaHandler`
2832
- ASP.NET Core integration: `AddRestate()` / `MapRestate()` DI extensions
2933
- Quick-start host: `RestateHost.CreateBuilder()`
3034
- Native AOT and trimming compatibility (`IsAotCompatible`, `IsTrimmable`)
3135
- 4 sample applications: Greeter, Counter, TicketReservation, SignupWorkflow
3236
- BenchmarkDotNet microbenchmarks for protocol layer and serialization
37+
- Community files: CONTRIBUTING.md, DECISION_LOG.md, issue templates, CodeQL, Dependabot
3338

3439
### Fixed
3540

3641
- Protocol layer hang caused by incorrect `PipeReader.AdvanceTo(consumed, buffer.End)` — fixed to `AdvanceTo(consumed)`
3742
- `ProposeRunCompletion` encoding: raw bytes instead of nested Value
3843
- `CallCompletion` / `CallInvocationIdCompletion` IDs were swapped (0x800D/0x800E)
44+
45+
### Removed
46+
47+
- `RunOptions` struct (was empty, never used by the protocol layer)

CONTRIBUTING.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Contributing to Restate .NET SDK
2+
3+
Thank you for your interest in contributing! This is a community-driven project and all contributions are welcome.
4+
5+
## Prerequisites
6+
7+
- [.NET 10.0 SDK](https://dotnet.microsoft.com/download) or later
8+
- [Restate Server](https://restate.dev/get-restate/) (for end-to-end testing)
9+
10+
## Building
11+
12+
```bash
13+
dotnet build
14+
```
15+
16+
## Running Tests
17+
18+
```bash
19+
# All tests
20+
dotnet test
21+
22+
# Specific test project
23+
dotnet test test/Restate.Sdk.Tests
24+
dotnet test test/Restate.Sdk.Generators.Tests
25+
26+
# Specific test
27+
dotnet test --filter "FullyQualifiedName~ProtocolIntegrationTests"
28+
```
29+
30+
## Code Formatting
31+
32+
The CI pipeline enforces consistent formatting. Check locally:
33+
34+
```bash
35+
dotnet format --verify-no-changes
36+
```
37+
38+
Fix formatting issues:
39+
40+
```bash
41+
dotnet format
42+
```
43+
44+
## Project Structure
45+
46+
```
47+
src/
48+
├── Restate.Sdk/ Core SDK (context hierarchy, protocol, hosting)
49+
├── Restate.Sdk.Generators/ Roslyn source generator (netstandard2.0)
50+
├── Restate.Sdk.Testing/ Mock contexts for unit testing
51+
└── Restate.Sdk.Lambda/ AWS Lambda adapter
52+
test/
53+
├── Restate.Sdk.Tests/ Core SDK tests
54+
├── Restate.Sdk.Generators.Tests/ Generator tests
55+
└── Restate.Sdk.Benchmarks/ BenchmarkDotNet microbenchmarks
56+
samples/ Working sample applications
57+
```
58+
59+
## Pull Request Process
60+
61+
1. Fork the repository and create a feature branch
62+
2. Make your changes with tests
63+
3. Run `dotnet build && dotnet test` to verify
64+
4. Run `dotnet format` to fix formatting
65+
5. Submit a PR with a clear description of the change
66+
67+
## Coding Standards
68+
69+
- Follow existing code patterns and naming conventions
70+
- Add XML doc comments for public API surface
71+
- Use `file` access modifier for test-only types
72+
- Keep the public API minimal — use `internal` by default
73+
- Source generator changes require `dotnet clean` + rebuild (stale artifacts)
74+
75+
## Reporting Issues
76+
77+
- Use [GitHub Issues](https://github.com/BeshoyHindy/restate-sdk-dotnet/issues) for bugs and feature requests
78+
- Include Restate server version, .NET SDK version, and reproduction steps for bugs

DECISION_LOG.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Design Decision Log
2+
3+
This document records key design decisions for the Restate .NET SDK, including context,
4+
alternatives considered, and rationale.
5+
6+
## 1. Abstract Classes over Interfaces for Handler Parameters
7+
8+
**Decision:** Handlers accept abstract class parameters (`Context`, `ObjectContext`, etc.) rather than interfaces.
9+
10+
**Context:** The Java and Go SDKs use interfaces for their context types. We initially considered the same approach for .NET.
11+
12+
**Rationale:**
13+
- Abstract classes allow adding new methods without breaking existing implementations (interfaces require default interface methods, which have limitations in .NET)
14+
- The `HttpContext` pattern from ASP.NET Core uses abstract classes for the same reason
15+
- Mock contexts can subclass the abstract types directly
16+
- Interfaces are still extracted (`IContext`, `IObjectContext`, etc.) for utility methods, type constraints, and documentation
17+
18+
**Alternatives:**
19+
- Pure interfaces: rejected because adding methods would break user implementations
20+
- Default interface methods: considered, but limited tooling support and cannot hold state
21+
22+
## 2. Interface Hierarchy (Additive, Non-Breaking)
23+
24+
**Decision:** Extract interfaces mirroring the abstract class hierarchy: `IContext` > `ISharedObjectContext` > `IObjectContext` / `ISharedWorkflowContext` > `IWorkflowContext`.
25+
26+
**Context:** Interfaces are valuable for utility methods (`void DoWork(IContext ctx)`), generic constraints, and API documentation, even though handlers use abstract class parameters.
27+
28+
**Rationale:**
29+
- Enables Moq/NSubstitute mocking for helper methods and utilities
30+
- Provides clean API documentation surface
31+
- `IWorkflowContext` extends both `IObjectContext` and `ISharedWorkflowContext` via multiple interface inheritance
32+
- Interfaces expose only durable execution primitives; implementation details (DurableRandom, DurableConsole, typed client methods) stay on the abstract class
33+
34+
## 3. BaseContext Visibility: `internal` Instead of `protected`
35+
36+
**Decision:** Changed `SharedObjectContext.BaseContext` from `protected Context?` to `internal Context`.
37+
38+
**Context:** The keyed context classes (ObjectContext, WorkflowContext, etc.) delegate all base operations to an inner `Context` instance. This property was originally `protected`, leaking implementation detail to external subclassers.
39+
40+
**Rationale:**
41+
- `internal` hides the delegation pattern from the public API
42+
- Mock contexts in `Restate.Sdk.Testing` still access it via `InternalsVisibleTo`
43+
- Eliminates the nullable reference pattern (`BaseContext!`) by using `= null!` initializer
44+
- Simpler than a `ContextOperations` helper class, which wouldn't reduce override declarations
45+
46+
## 4. Composition for Mock Context Deduplication
47+
48+
**Decision:** Use `MockContextHelper` (composition) rather than a shared base class for mock context deduplication.
49+
50+
**Context:** Four keyed mock contexts (MockObjectContext, MockSharedObjectContext, MockWorkflowContext, MockSharedWorkflowContext) had ~320 lines of duplicated delegation code.
51+
52+
**Rationale:**
53+
- Mock contexts already inherit from abstract context classes (e.g., `MockObjectContext : ObjectContext`), so C# single inheritance prevents a shared mock base class
54+
- `MockContextHelper` is an internal composition class that holds the inner `MockContext` and exposes shared setup methods
55+
- Each keyed mock delegates to `_helper` for calls/sends/sleeps and to specialized stores for state/promises
56+
- Reduced ~320 lines of duplication while maintaining the same public API
57+
58+
## 5. HandlerAttributeBase Extraction
59+
60+
**Decision:** Extract common properties from `[Handler]` and `[SharedHandler]` into abstract `HandlerAttributeBase`.
61+
62+
**Context:** Both handler attributes had 6 identical properties (Name, InactivityTimeout, AbortTimeout, IdempotencyRetention, JournalRetention, IngressPrivate).
63+
64+
**Rationale:**
65+
- Eliminates property duplication — new handler options only need to be added once
66+
- Source generator is unaffected: it matches concrete type names (`HandlerAttribute`, `SharedHandlerAttribute`) and reads properties via `NamedArguments`, which works with inherited properties
67+
- Named `HandlerAttributeBase` (not `HandlerAttributeAttribute`) because it's a base for attributes, not an attribute itself
68+
69+
## 6. RunOptions Removal
70+
71+
**Decision:** Delete the `RunOptions` struct and remove the parameter from `Run()` overloads.
72+
73+
**Context:** `RunOptions` was an empty `readonly record struct` with zero properties. Investigation confirmed that `DefaultContext.Run()` completely ignores the options parameter — `InvocationStateMachine.WriteRunCommand()` accepts only a name string.
74+
75+
**Rationale:**
76+
- Dead code in a pre-1.0 alpha — removing is better than keeping a misleading empty type
77+
- If retry/retention options are needed later, they can be re-added when protocol support is wired through
78+
- Clean API is more important than speculative future compatibility
79+
80+
## 7. Source Generator Hardening
81+
82+
**Decision:** Add `#pragma warning disable` to generated files, null checks in deserializers, and compile-time TimeSpan validation (RESTATE009).
83+
84+
**Rationale:**
85+
- `#pragma warning disable` prevents user analyzer configurations from producing spurious warnings on generated code
86+
- Null checks in deserializer lambdas provide clear error messages instead of NullReferenceExceptions
87+
- RESTATE009 catches invalid TimeSpan strings (e.g., `[Handler(InactivityTimeout = "invalid")]`) at compile time rather than runtime
88+
89+
## 8. Deterministic Time in Mock Contexts
90+
91+
**Decision:** `MockContext.Now()` returns a configurable `CurrentTime` property (default: 2024-01-01T00:00:00Z) instead of `DateTimeOffset.UtcNow`.
92+
93+
**Rationale:**
94+
- Tests that depend on time should be deterministic
95+
- Users can set `ctx.CurrentTime = ...` to simulate specific timestamps
96+
- Default is a fixed date to make test output predictable
97+
98+
## 9. .NET 10.0 Target Framework
99+
100+
**Decision:** Target `net10.0` exclusively (no multi-targeting).
101+
102+
**Context:** The SDK uses C# 14 features and .NET 10 APIs. Multi-targeting older frameworks would require conditional compilation and feature polyfills.
103+
104+
**Rationale:**
105+
- Restate is a modern infrastructure platform — users are expected to use current .NET versions
106+
- Single target simplifies the build, eliminates `#if` directives, and allows using the latest APIs
107+
- .NET 10 is the current LTS release
108+
109+
## 10. Typed Client Registration for Testing
110+
111+
**Decision:** Add `RegisterClient<TClient>()` to mock contexts instead of auto-generating mock clients.
112+
113+
**Rationale:**
114+
- Source-generated typed clients depend on internal context wiring that doesn't exist in mocks
115+
- `RegisterClient<TClient>()` lets users provide hand-crafted or Moq-based client instances
116+
- Without registration, client methods throw `NotSupportedException` with a helpful message
117+
- Simple and explicit — no magic or auto-generation needed
118+
119+
## Comparison with Official SDKs
120+
121+
| Feature | Java | TypeScript | Go | Rust | Python | **.NET (this)** |
122+
|---------|------|------------|-----|------|--------|-----------------|
123+
| Context types | Interfaces | Classes | Interfaces | Traits | Classes | **Abstract classes + interfaces** |
124+
| Code generation | Annotation processor | None | codegen CLI | Proc macros | None | **Roslyn source generator** |
125+
| Testing | TestRestateRuntime | Mock utilities | Minimal | Minimal | Minimal | **Mock context classes** |
126+
| Handler registration | Annotation scan | Manual | Manual | Attribute macros | Decorators | **Source generator + attributes** |
127+
| State typing | StateKey | String keys | String keys | String keys | String keys | **StateKey\<T\>** |
128+
| Protocol version | v5-v6 | v5-v6 | v5-v6 | v5-v6 | v5-v6 | **v5-v6** |

0 commit comments

Comments
 (0)