Skip to content

Commit fa9f0aa

Browse files
authored
Merge pull request #20 from runemalm/develop
Release v3.0.0-beta.1
2 parents 49ea084 + 2c3e6e6 commit fa9f0aa

131 files changed

Lines changed: 3641 additions & 408 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export $(shell sed 's/=.*//' env.make)
1818
HOME := $(shell echo ~)
1919
PWD := $(shell pwd)
2020
NETWORK := openddd-net
21-
BUILD_VERSION := 3.0.0-alpha.4
21+
BUILD_VERSION := 3.0.0-beta.1
2222

2323
NUGET_NAME := OpenDDD.NET
2424
ROOT_NAMESPACE := OpenDDD

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
OpenDDD.NET is an open-source framework for domain-driven design (DDD) development using C# and .NET. It provides a set of powerful tools and abstractions to help developers build scalable, maintainable, and testable applications following the principles of DDD.
77

8-
> **Note:** OpenDDD.NET is currently in an alpha state as part of new major version 3. Use with caution in production environments.
8+
> **Note:** OpenDDD.NET is currently in a beta state as part of new major version 3. Use with caution in production environments.
99
1010
⭐ Consider **starring** and/or **following** the project to stay updated with the latest developments.
1111

@@ -28,8 +28,6 @@ We're adhering to the key principles and building blocks of Domain-Driven Design
2828

2929
## Supported Versions
3030

31-
- ASP.NET Core 6
32-
- ASP.NET Core 7
3331
- ASP.NET Core 8
3432
- ASP.NET Core 9
3533

@@ -57,7 +55,7 @@ To get started with OpenDDD.NET, follow these simple steps:
5755
var builder = WebApplication.CreateBuilder(args);
5856

5957
// Add OpenDDD services
60-
builder.Services.AddOpenDDD<BookstoreDbContext>(builder.Configuration, options =>
58+
builder.Services.AddOpenDDD(builder.Configuration, options =>
6159
{
6260
options.UseInMemoryDatabase()
6361
.UseInMemoryMessaging()
@@ -94,6 +92,12 @@ Explore the project in the repository: [Bookstore Sample Project](https://github
9492

9593
## Release History
9694

95+
**3.0.0-beta.1 (2025-02-17)**
96+
97+
- **Beta Release**: OpenDDD.NET has moved from alpha to `beta`, indicating improved stability.
98+
- **Performance Improvements**: Optimized framework components for better efficiency.
99+
- **Fix Issues**: Fixed various issues.
100+
97101
**3.0.0-alpha.4 (2025-02-15)**
98102

99103
- **Persistence Providers**: Add the `OpenDDD` persistence provider.

docs/building-blocks.rst

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.. note::
22

3-
OpenDDD.NET is currently in alpha. Features and documentation are under active development and subject to change.
3+
OpenDDD.NET is currently in beta. Features and documentation are under active development and subject to change.
44

55
.. _building-blocks:
66

@@ -57,7 +57,7 @@ An **Aggregate Root** is the entry point to an aggregate. It enforces invariants
5757
Entity
5858
------
5959

60-
An **Entity** has a unique identity within an aggregate and a lifecycle managed by its Aggregate Root.
60+
An **Entity** has a unique identity and a lifecycle managed by its Aggregate Root.
6161

6262
.. code-block:: csharp
6363
@@ -181,16 +181,18 @@ Auto-Registration
181181

182182
Repositories are **auto-registered** with `IRepository<TAggregateRoot, TId>`. If a custom repository interface exists (e.g., `ICustomerRepository`), it is registered with its corresponding implementation instead.
183183

184-
**Example: Default Auto-Registered Repository**
184+
**Example: Default Auto-Registered Repositories**
185185

186186
- `IRepository<Guid, Customer>` → `PostgresOpenDddRepository<Guid, Customer>`
187187
- `IRepository<Guid, Customer>` → `EfCoreRepository<Guid, Customer>`
188188

189-
**Example: Custom Auto-Registered Repository**
189+
**Example: Custom Auto-Registered Repositories**
190190

191191
- `ICustomerRepository` → `PostgresOpenDddCustomerRepository`
192192
- `ICustomerRepository` → `EfCoreCustomerRepository`
193193

194+
**NOTE:** If you have more than one implementation of a repository the framework won't know which of them to auto-register. In this case you need to delete one of the implementations or disable auto-registration and register the implementation you want manually.
195+
194196
Auto-registration can be **disabled in the configuration**.
195197

196198
Create a Custom Repository
@@ -212,11 +214,9 @@ If an aggregate requires additional query methods, create a **custom repository*
212214
{
213215
public class PostgresOpenDddCustomerRepository : PostgresOpenDddRepository<Customer, Guid>, ICustomerRepository
214216
{
215-
private readonly ILogger<PostgresOpenDddCustomerRepository> _logger;
216-
217217
public PostgresOpenDddCustomerRepository(
218218
PostgresDatabaseSession session,
219-
IAggregateSerializer serializer)
219+
IAggregateSerializer serializer)
220220
: base(session, serializer)
221221
{
222222
@@ -232,26 +232,42 @@ If an aggregate requires additional query methods, create a **custom repository*
232232
Using EF Core
233233
-------------
234234

235-
By default, OpenDDD.NET uses its **custom persistence provider**, which follows a **document storage model**. This aligns closely with **DDD aggregate patterns** (including Alistair Cockburn’s **Entity pattern**) by storing aggregates **as serialized JSON documents**.
235+
By default, OpenDDD.NET employs a **custom persistence provider** that stores aggregates as **serialized JSON documents**. This approach aligns with **DDD aggregate patterns** and Pat Helland's **Entity pattern** (see `Life Beyond Distributed Transactions <https://queue.acm.org/detail.cfm?id=3025012>`_), ensuring transactional consistency within each aggregate.
236+
237+
For relational storage, OpenDDD.NET supports EF Core as an alternative persistence provider. To use EF Core, you need to:
236238

237-
If you need **relational storage**, you can configure **EF Core** as the persistence provider. In that case, you must define:
239+
- Set the persistence provider to `EFCore` in the configuration.
240+
- Create a subclass of `OpenDddDbContextBase`.
241+
- Define entity mappings using `EfAggregateRootConfigurationBase` for aggregates.
242+
- Define entity mappings using `EfEntityConfigurationBase` for entities.
243+
- Implement custom repositories by subclassing `EfCoreRepository<TAggregateRoot, TId>`.
244+
- Register your custom `DbContext` using the `AddOpenDdd<TDbContext>` overload.
245+
- Ensure aggregates and entities have a **parameterless private constructor** so EF Core can instantiate them.
246+
247+
Example JSON configuration:
248+
249+
.. code-block:: json
238250
239-
- A subclass of `OpenDddDbContextBase`
240-
- Subclasses of `EfAggregateRootConfigurationBase` for aggregates
241-
- Subclasses of `EfEntityConfigurationBase` for entities
242-
- Subclasses of `EfCoreRepository<TAggregateRoot, TId>` for custom repositories
243-
- Use the `AddOpenDdd<TDbContext>` overload when registering OpenDDD to specify your custom DbContext
251+
{
252+
"OpenDDD": {
253+
"PersistenceProvider": "EFCore",
254+
"DatabaseProvider": "Postgres",
255+
"Postgres": {
256+
"ConnectionString": "Host=localhost;Port=5432;Database=bookstore;Username=postgres;Password=password"
257+
}
258+
}
259+
}
244260
245261
See the `Bookstore Sample Project <https://github.com/runemalm/OpenDDD.NET/tree/master/samples/Bookstore/src/Bookstore/Infrastructure/Persistence/EfCore>`_ for examples.
246262

247263
Summary
248264
-------
249265

266+
- **OpenDDD.NET includes a built-in persistence provider**, which is used by default and stores aggregates as **serialized JSON documents**.
250267
- Repositories implement `IRepository<TAggregateRoot, TId>`, ensuring a **consistent API**.
251-
- Aggregates are stored as **JSON documents** in the configured database.
252268
- **Auto-registration** registers repositories unless overridden by a custom interface.
253269
- **Custom repositories** can be created by subclassing a base repository class.
254-
- **EF Core** can be used instead by configuring it properly.
270+
- **EF Core** can be used for relational storage by configuring it properly.
255271

256272
.. _building-blocks-actions-and-commands:
257273

@@ -349,7 +365,7 @@ A **Domain Event** represents a significant change within the domain.
349365
Integration Events
350366
------------------
351367

352-
An **Integration Event** notifies external bounded contexts of domain changes. It is part of your **interchange context** project.
368+
An **Integration Event** is used to communicate between bounded contexts. It is part of an **interchange context**.
353369

354370
**Defining an Integration Event:**
355371

@@ -382,6 +398,7 @@ Events are published using `IDomainPublisher` (for domain events) or `IIntegrati
382398
.. code-block:: csharp
383399
384400
using OpenDDD.Domain.Model;
401+
using OpenDDD.Domain.Model.Exception;
385402
using Bookstore.Domain.Model;
386403
using Bookstore.Domain.Model.Events;
387404
@@ -400,16 +417,10 @@ Events are published using `IDomainPublisher` (for domain events) or `IIntegrati
400417
401418
public async Task<Customer> RegisterAsync(string name, string email, CancellationToken ct)
402419
{
403-
if (string.IsNullOrWhiteSpace(name))
404-
throw new ArgumentException("Customer name cannot be empty.", nameof(name));
405-
406-
if (string.IsNullOrWhiteSpace(email))
407-
throw new ArgumentException("Customer email cannot be empty.", nameof(email));
408-
409420
var existingCustomer = await _customerRepository.FindByEmailAsync(email, ct);
410421
411422
if (existingCustomer != null)
412-
throw new InvalidOperationException($"A customer with the email '{email}' already exists.");
423+
throw new EntityExistsException("Customer", $"email '{email}'");
413424
414425
var newCustomer = Customer.Create(name, email);
415426
@@ -518,8 +529,8 @@ A **listener group** defines a set of **competing consumers** for a topic. Each
518529
Summary
519530
-------
520531

521-
- **Domain Events** capture internal changes within aggregates.
522-
- **Integration Events** notify external systems of changes.
532+
- **Domain Events** capture changes within a domain.
533+
- **Integration Events** is used to communicate between bounded contexts.
523534
- **Publishers** (`IDomainPublisher`, `IIntegrationPublisher`) send events.
524535
- **Listeners** react to events asynchronously.
525536
- **Topics** are configurable in `OpenDddOptions`.
@@ -534,9 +545,8 @@ A **Domain Service** provides domain-specific logic that **does not fit within a
534545

535546
Domain services are typically used when:
536547

537-
- The logic **does not belong to an aggregate (entities or value objects)**.
538-
- The operation involves **external dependencies** (e.g., repositories, external services) but is still **domain logic**.
539-
- Business rules need to be **shared across multiple use cases**.
548+
- The logic **does not belong naturally to an aggregate (entities or value objects)**.
549+
- Business rules need to be **shared across multiple use cases**.
540550

541551
**Example domain service interface:**
542552

@@ -558,6 +568,7 @@ Domain services are typically used when:
558568
.. code-block:: csharp
559569
560570
using OpenDDD.Domain.Model;
571+
using OpenDDD.Domain.Model.Exception;
561572
using Bookstore.Domain.Model;
562573
using Bookstore.Domain.Model.Events;
563574
@@ -579,7 +590,7 @@ Domain services are typically used when:
579590
var existingCustomer = await _customerRepository.FindByEmailAsync(email, ct);
580591
581592
if (existingCustomer != null)
582-
throw new InvalidOperationException($"A customer with the email '{email}' already exists.");
593+
throw new EntityExistsException("Customer", $"email '{email}'");
583594
584595
var newCustomer = Customer.Create(name, email);
585596
@@ -597,12 +608,11 @@ Domain services are typically used when:
597608

598609
- They contain **domain logic** but **are not part of an aggregate**.
599610
- They do **not manage state**; they operate on domain objects.
600-
- They can depend on **repositories, domain events, or external services** via ports.
601611

602612
Domain services **should not** be used for:
603613

604614
- Simple operations that belong to an **aggregate root**.
605-
- Coordinating application workflows (use **actions** instead).
615+
- Coordinating application workflows (use **actions** and **domain events** instead).
606616
- Infrastructure concerns like logging or email (use **infrastructure services**).
607617

608618
.. _building-blocks-infrastructure-services:

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
version = "3.0"
1515

1616
# The full version, including alpha/beta/rc tags
17-
release = '3.0.0-alpha.4'
17+
release = '3.0.0-beta.1'
1818

1919
# -- General configuration ---------------------------------------------------
2020
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

docs/configuration.rst

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.. note::
2+
3+
OpenDDD.NET is currently in beta. Features and documentation are under active development and subject to change.
4+
15
.. _config:
26

37
===================
@@ -22,8 +26,8 @@ An example configuration in `appsettings.json`:
2226
2327
{
2428
"OpenDDD": {
25-
"PersistenceProvider": "EfCore",
26-
"DatabaseProvider": "SQLite",
29+
"PersistenceProvider": "OpenDdd",
30+
"DatabaseProvider": "InMemory",
2731
"MessagingProvider": "InMemory",
2832
"Events": {
2933
"DomainEventTopicTemplate": "Bookstore.Domain.{EventName}",
@@ -70,11 +74,10 @@ Instead of using `appsettings.json`, OpenDDD.NET can be configured **dynamically
7074

7175
.. code-block:: csharp
7276
73-
builder.Services.AddOpenDDD<BookstoreDbContext>(builder.Configuration,
77+
builder.Services.AddOpenDDD(builder.Configuration,
7478
options =>
7579
{
76-
options.UseEfCore()
77-
.UseSQLite("DataSource=Infrastructure/Persistence/EfCore/Bookstore.db;Cache=Shared")
80+
options.UseInMemoryDatabase()
7881
.UseInMemoryMessaging()
7982
.SetEventTopicTemplates(
8083
"Bookstore.Domain.{EventName}",
@@ -89,26 +92,33 @@ Instead of using `appsettings.json`, OpenDDD.NET can be configured **dynamically
8992
Persistence Configuration
9093
-------------------------
9194

92-
OpenDDD.NET supports multiple persistence providers:
95+
OpenDDD.NET supports multiple persistence providers. Each persistence provider supports a set of database providers.
96+
97+
Example Configurations:
98+
99+
**OpenDDD Persistence Provider**:
100+
101+
.. code-block:: csharp
102+
103+
// With PostgreSQL
104+
options.UsePostgres("Host=localhost;Port=5432;Database=bookstore;Username=postgres;Password=password");
105+
106+
// With In-Memory
107+
options.UseInMemory();
93108
94109
**EF Core Persistence Provider**:
95110

96111
.. code-block:: csharp
97112
113+
// With SQLite
98114
options.UseEfCore().UseSQLite("DataSource=Bookstore.db;Cache=Shared");
99115
100-
// PostgreSQL
116+
// With PostgreSQL
101117
options.UseEfCore().UsePostgres("Host=localhost;Port=5432;Database=bookstore;Username=postgres;Password=password");
102118
103-
// SQL Server
119+
// With SQL Server
104120
options.UseEfCore().UseSqlServer("Server=localhost;Database=bookstore;User Id=sa;Password=password;");
105121
106-
**OpenDDD Persistence Provider**:
107-
108-
.. code-block:: csharp
109-
110-
options.UseOpenDddPersistence().UsePostgres("Host=localhost;Port=5432;Database=bookstore;Username=postgres;Password=password");
111-
112122
-----------------------
113123
Messaging Configuration
114124
-----------------------

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.. note::
22

3-
OpenDDD.NET is currently in alpha. Features and documentation are under active development and subject to change.
3+
OpenDDD.NET is currently in beta. Features and documentation are under active development and subject to change.
44

55
OpenDDD.NET
66
===========

docs/releases.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
.. note::
22

3-
OpenDDD.NET is currently in alpha. Features and documentation are under active development and subject to change.
3+
OpenDDD.NET is currently in beta. Features and documentation are under active development and subject to change.
44

55
###############
66
Version History
77
###############
88

9+
**3.0.0-beta.1 (2025-02-17)**
10+
11+
- **Beta Release**: OpenDDD.NET has moved from alpha to **beta**, indicating improved stability.
12+
- **Performance Improvements**: Optimized framework components for better efficiency.
13+
- **Fix Issues**: Fixed various issues.
14+
915
**3.0.0-alpha.4 (2025-02-15)**
1016

1117
- **Persistence Providers**: Add the `OpenDDD` persistence provider.
@@ -15,7 +21,7 @@ Version History
1521
- **Project Template**: Add a project template nuget for quick scaffolding of a new project.
1622
- **Documentation**: Refactor the documentation to reflect new changes and improve onboarding experience.
1723
- **Namespace**: Change the name of the namespace `Main` -> `API`.
18-
- **Fix issues**: Fix issues with the `Azure Service Bus` provider & the `Ef Core` base repository.
24+
- **Fix Issues**: Fix issues with the `Azure Service Bus` provider & the `Ef Core` base repository.
1925

2026
**3.0.0-alpha.3 (2025-01-30)**
2127

0 commit comments

Comments
 (0)