From c8a52c3079d85cff08853579d1a6269668d3048f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Fija=C5=82kowski?= Date: Thu, 14 Sep 2023 11:17:12 +0200 Subject: [PATCH] Reorganize documentation for MkDocs & ReadTheDocs integration --- .gitignore | 3 + .readthedocs.yaml | 11 ++++ docs/README.md | 11 ++-- docs/adrs/2021-02-04_value_object.md | 2 +- .../adrs/2021-11-25_optimistic_concurrency.md | 2 +- docs/adrs/README.md | 5 +- docs/basics/README.md | 6 +- docs/basics/{01_app.md => app.md} | 3 + docs/basics/{02_cqrs.md => cqrs.md} | 17 +++-- ...gration.md => mass_transit_integration.md} | 4 +- docs/benchmarks/2018-07-25.md | 57 ---------------- .../2018-12-17_01_before_ctx_removal.md | 55 ---------------- .../2018-12-17_02_after_ctx_removal.md | 55 ---------------- docs/benchmarks/2019-12-30.md | 55 ---------------- docs/benchmarks/README.md | 6 -- docs/domain/README.md | 7 +- .../{03_aggregates.md => aggregates.md} | 20 +++--- docs/domain/ids.md | 16 +++-- docs/general/README.md | 8 +-- ...and-testing.md => building_and_testing.md} | 0 ...ject-structure.md => project_structure.md} | 8 ++- ...ix.md => versioning_and_support_matrix.md} | 0 docs/guides/00_intro.md | 3 - docs/guides/0X_authorization.md | 1 - docs/guides/0X_handling_events.md | 1 - docs/guides/0X_pipeline.md | 1 - docs/guides/README.md | 17 +++++ .../v8_migration_guide.md} | 9 +-- docs/guides/basic/authorization.md | 3 + .../creating_an_aggregate.md} | 4 +- docs/guides/basic/handling_events.md | 3 + docs/guides/basic/pipeline.md | 3 + .../writing_commands.md} | 20 +++--- .../writing_queries.md} | 2 +- .../force_update.md} | 9 ++- docs/guides/src/CQRS/AddTasksToProjectCH.cs | 65 ------------------- docs/guides/src/CQRS/AllProjectsQH.cs | 18 ----- docs/guides/src/CQRS/CreateProjectCH.cs | 31 --------- .../guides/src/Contracts/AddTasksToProject.cs | 24 ------- docs/guides/src/Contracts/AllProjects.cs | 11 ---- docs/guides/src/Contracts/CreateProject.cs | 11 ---- .../src/DataAccess/ProjectsRepository.cs | 11 ---- docs/guides/src/Domain/Project.cs | 44 ------------- docs/guides/src/Domain/Task.cs | 50 -------------- docs/guides/src/Domain/User.cs | 20 ------ mkdocs.yml | 43 ++++++++++++ 46 files changed, 172 insertions(+), 583 deletions(-) create mode 100644 .readthedocs.yaml rename docs/basics/{01_app.md => app.md} (96%) rename docs/basics/{02_cqrs.md => cqrs.md} (94%) rename docs/basics/{03_mass_transit_integration.md => mass_transit_integration.md} (98%) delete mode 100644 docs/benchmarks/2018-07-25.md delete mode 100644 docs/benchmarks/2018-12-17_01_before_ctx_removal.md delete mode 100644 docs/benchmarks/2018-12-17_02_after_ctx_removal.md delete mode 100644 docs/benchmarks/2019-12-30.md delete mode 100644 docs/benchmarks/README.md rename docs/domain/{03_aggregates.md => aggregates.md} (53%) rename docs/general/{02_building-and-testing.md => building_and_testing.md} (100%) rename docs/general/{03_project-structure.md => project_structure.md} (78%) rename docs/general/{01_versioning-and-support-matrix.md => versioning_and_support_matrix.md} (100%) delete mode 100644 docs/guides/00_intro.md delete mode 100644 docs/guides/0X_authorization.md delete mode 100644 docs/guides/0X_handling_events.md delete mode 100644 docs/guides/0X_pipeline.md create mode 100644 docs/guides/README.md rename docs/guides/{0X_v8_migration_guide.md => advanced/v8_migration_guide.md} (97%) create mode 100644 docs/guides/basic/authorization.md rename docs/guides/{01_creating_an_aggregate.md => basic/creating_an_aggregate.md} (96%) create mode 100644 docs/guides/basic/handling_events.md create mode 100644 docs/guides/basic/pipeline.md rename docs/guides/{02_writing_commands.md => basic/writing_commands.md} (89%) rename docs/guides/{03_writing_queries.md => basic/writing_queries.md} (96%) rename docs/guides/{02_force_update.md => features/force_update.md} (58%) delete mode 100644 docs/guides/src/CQRS/AddTasksToProjectCH.cs delete mode 100644 docs/guides/src/CQRS/AllProjectsQH.cs delete mode 100644 docs/guides/src/CQRS/CreateProjectCH.cs delete mode 100644 docs/guides/src/Contracts/AddTasksToProject.cs delete mode 100644 docs/guides/src/Contracts/AllProjects.cs delete mode 100644 docs/guides/src/Contracts/CreateProject.cs delete mode 100644 docs/guides/src/DataAccess/ProjectsRepository.cs delete mode 100644 docs/guides/src/Domain/Project.cs delete mode 100644 docs/guides/src/Domain/Task.cs delete mode 100644 docs/guides/src/Domain/User.cs create mode 100644 mkdocs.yml diff --git a/.gitignore b/.gitignore index 13a866825..b74d64a1b 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,6 @@ packed .paket/Paket.Restore.targets **/BenchmarkDotNet.Artifacts + +# Python venv for MkDocs +.env diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..d2d355736 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,11 @@ +# Read the Docs configuration file for MkDocs projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +mkdocs: + configuration: mkdocs.yml diff --git a/docs/README.md b/docs/README.md index 5c371a3b4..e5c2dcfcb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,15 +16,14 @@ Even though is a framework, we try to stick to the ASP.NET Core model as close a The CoreLib documentation is available here: - 1. [General](./general/README.md), - 2. [Basics](./basics/README.md), - 3. [Benchmarks](./benchmarks/README.md). - 4. [Domain](./domain/README.md). - 5. [Architecture decision records](./adrs/README.md). +1. [General](./general/README.md), +2. [Basics](./basics/README.md), +3. [Domain](./domain/README.md). +4. [Architecture decision records](./adrs/README.md). ## Domain Driven Design LeanCode Core Library is strongly based on concepts of Domain Driven Design. If you are not familiar with this approach to developing software, you can check these books: 1. Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans -2. Implementing Domain-Driven Design, Vaughn Vernon \ No newline at end of file +2. Implementing Domain-Driven Design, Vaughn Vernon diff --git a/docs/adrs/2021-02-04_value_object.md b/docs/adrs/2021-02-04_value_object.md index 4ca2736a1..fae36fc83 100644 --- a/docs/adrs/2021-02-04_value_object.md +++ b/docs/adrs/2021-02-04_value_object.md @@ -1,4 +1,4 @@ -# How to implement value objects in our domains +# Value Objects While modelling a domain we use the concept of value objects. The issue is how to represent value objects in code, as described in DDD. diff --git a/docs/adrs/2021-11-25_optimistic_concurrency.md b/docs/adrs/2021-11-25_optimistic_concurrency.md index 57acf968e..8ce417b0a 100644 --- a/docs/adrs/2021-11-25_optimistic_concurrency.md +++ b/docs/adrs/2021-11-25_optimistic_concurrency.md @@ -1,4 +1,4 @@ -# How to handle concurrency token columns in non–MSSQL databases +# Optimistic Concurrency The `IOptimisticConcurrency` interface requires `DateTime DateModified` and `byte[] RowVersion` properties to be implemented by aggregate roots. However, the `byte[]` representation of `RowVersion` column is specific to Microsoft SQL Server and is not compatible with other databases, such as PostgreSQL and its convention to use `uint xmin` hidden column as a concurrency token. diff --git a/docs/adrs/README.md b/docs/adrs/README.md index 9a486991b..5f032ef0a 100644 --- a/docs/adrs/README.md +++ b/docs/adrs/README.md @@ -1,3 +1,6 @@ # Architecture decision records - 1. [Value objects](./2021-02-04_value_object.md), +Here, you can find a list list of [ADRs](https://adr.github.io/) that we prepared for the CoreLibrary project. The list is non-exhaustive and resembles only some of our decisions, but serves the puprose of displaying how we approach creating ADRs. + +1. [Value objects](./2021-02-04_value_object.md), +2. [Optimistic Concurrency](./2021-11-25_optimistic_concurrency.md). diff --git a/docs/basics/README.md b/docs/basics/README.md index b9e4ebc9c..b1c48aac3 100644 --- a/docs/basics/README.md +++ b/docs/basics/README.md @@ -2,6 +2,6 @@ Here, we describe basic elements of CoreLib-based projects: - 1. [CoreLib-based apps](./01_app.md), - 2. [CQRS](./02_cqrs.md), - 3. [MassTransit](./03_mass_transit_integration.md). + 1. [CoreLib-based apps](./app.md), + 2. [CQRS](./cqrs.md), + 3. [MassTransit](./mass_transit_integration.md). diff --git a/docs/basics/01_app.md b/docs/basics/app.md similarity index 96% rename from docs/basics/01_app.md rename to docs/basics/app.md index 95e1c55f6..15596abe5 100644 --- a/docs/basics/01_app.md +++ b/docs/basics/app.md @@ -1,5 +1,8 @@ # CoreLib-based apps +!!! warning "Outdated" + This document is slightly outdated and describes v7 version of the library. v8 revampend some of the things here. + CoreLib tries to make developing ASP.NET Core-based apps easier. One of the goals is to provide common features out of the box (e.g. logging & config) and unify how the app is being composed. Here, we describe what is being done and how it affects app structure. ## Used libraries diff --git a/docs/basics/02_cqrs.md b/docs/basics/cqrs.md similarity index 94% rename from docs/basics/02_cqrs.md rename to docs/basics/cqrs.md index 8a3738192..87d012b22 100644 --- a/docs/basics/02_cqrs.md +++ b/docs/basics/cqrs.md @@ -1,5 +1,8 @@ # CQRS +!!! warning "Outdated" + This document is slightly outdated and describes v7 version of the library. v8 revampend some of the things here. + We use CQRS in almost every project. We try to stick to it as much as possible because it allows us to easily construct our APIs (using RemoteCQRS) and makes our code maintainable. Here, we present a short description on what CQRS is and how we designed it. This is not an extensive CQRS description. :) @@ -99,12 +102,12 @@ public class CreateDishCH : ICommandHandler As you can see, the command handler is really simple - it just converts the command into new aggregate, tracking who owns the dish (`UserId` - they are the ones that have `CreateDish` permission). That does not mean this is the only responsibility of the handlers (it's just an example), but there are some guidelines related to them: -1. Keep them simple and testable, do not try to model whole flows with a single command, -2. Commands should rely on aggregates to gather the data (try not to use queries inside command handlers), -3. Commands should modify just a single aggregate (try to `AddAsync`/`UpdateAsync`/`DeleteAsync` at most once), -4. If the business process requires to modify multiple aggregates, try to use events and event handlers (but don't over-engineer), -5. If that does not help, modify/add/delete multiple aggregates, -6. Do not throw exceptions from inside commands. The client will receive generic error (`500 Internal Server Error`). Do it only as a last resort. +1. Keep them simple and testable, do not try to model whole flows with a single command, +2. Commands should rely on aggregates to gather the data (try not to use queries inside command handlers), +3. Commands should modify just a single aggregate (try to `AddAsync`/`UpdateAsync`/`DeleteAsync` at most once), +4. If the business process requires to modify multiple aggregates, try to use events and event handlers (but don't over-engineer), +5. If that does not help, modify/add/delete multiple aggregates, +6. Do not throw exceptions from inside commands. The client will receive generic error (`500 Internal Server Error`). Do it only as a last resort. #### Validation @@ -293,7 +296,7 @@ All commands, queries and operations must derive from those interfaces and so th RemoteCQRS request example: -``` +```bash curl -X POST \ https://api.local.lncd.pl/api/query/Full.Object.Namespace.Name.FindDishesMatchingName \ -H 'Content-Type: application/json' \ diff --git a/docs/basics/03_mass_transit_integration.md b/docs/basics/mass_transit_integration.md similarity index 98% rename from docs/basics/03_mass_transit_integration.md rename to docs/basics/mass_transit_integration.md index 3e47268a2..8927780d4 100644 --- a/docs/basics/03_mass_transit_integration.md +++ b/docs/basics/mass_transit_integration.md @@ -17,9 +17,9 @@ Relay module comes with a set of filters, mainly to ensure that events raised in The filters are: - `CorrelationFilter` - enriches logs with message ids, correlation ids and consumer types -- `ConsumeMessagesOnceFilter`\* - see the [inbox](#Inbox) +- `ConsumeMessagesOnceFilter`\* - see the [inbox](#inbox) - `EventsPublisherFilter`\* - relays Domain Events raised further in the pipeline to the bus -- `StoreAndPublishEventsFilter`\* - as above, additionally implements [outbox](#Outbox) +- `StoreAndPublishEventsFilter`\* - as above, additionally implements [outbox](#outbox) CAUTION: Filters marked with \* are registered via configuration observers - they are applied after other regular filters. This will cause an counterintuitive filters order if do not register them as last in the pipeline. diff --git a/docs/benchmarks/2018-07-25.md b/docs/benchmarks/2018-07-25.md deleted file mode 100644 index 414e62e61..000000000 --- a/docs/benchmarks/2018-07-25.md +++ /dev/null @@ -1,57 +0,0 @@ -# 2018-07-25 - -``` ini - -BenchmarkDotNet=v0.11.0, OS=arch -Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=2.1.802 - [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT - Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT - -Job=Core Runtime=Core - -``` - -## Pipelines - -| Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | -|--------------------- |------------:|----------:|----------:|-------:|---------:|-------:|----------:| -| Empty | 62.16 ns | 0.3131 ns | 0.2776 ns | 1.00 | 0.00 | 0.0228 | 72 B | -| SingleElement | 70.43 ns | 0.2986 ns | 0.2647 ns | 1.13 | 0.01 | 0.0228 | 72 B | -| EmptyAutofac | 986.69 ns | 6.0325 ns | 5.3477 ns | 15.87 | 0.11 | 0.4368 | 1376 B | -| SingleElementAutofac | 1,384.62 ns | 5.6249 ns | 5.2615 ns | 22.28 | 0.13 | 0.5856 | 1848 B | - -## InProcCQRS__Commands - -| Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | -|-------------------------------------------- |----------:|----------:|----------:|-------:|---------:|-------:|----------:| -| CommandWithInlineObjContext | 3.622 us | 0.0366 us | 0.0305 us | 1.00 | 0.00 | 1.0567 | 3.26 KB | -| CommandWithoutInlineObjContext | 4.317 us | 0.0212 us | 0.0198 us | 1.19 | 0.01 | 1.2512 | 3.85 KB | -| PlainCommandWithSecuredPipeline | 7.812 us | 0.0288 us | 0.0256 us | 2.16 | 0.02 | 1.6632 | 5.14 KB | -| SecuredCommandWithSecuredPipeline | 14.562 us | 0.0677 us | 0.0633 us | 4.02 | 0.04 | 2.2583 | 6.98 KB | -| SecuredButFailingCommandWithSecuredPipeline | 15.360 us | 0.0581 us | 0.0543 us | 4.24 | 0.04 | 2.2888 | 7.05 KB | -| PlainCommandWithValidatedPipeline | 7.665 us | 0.0405 us | 0.0379 us | 2.12 | 0.02 | 1.9836 | 6.1 KB | -| ValidCommandWithValidatedPipeline | 11.302 us | 0.0415 us | 0.0347 us | 3.12 | 0.03 | 2.7618 | 8.53 KB | -| InvalidCommandWithValidatedPipeline | 11.399 us | 0.0775 us | 0.0687 us | 3.15 | 0.03 | 2.6093 | 8.04 KB | - -## InProcCQRS__Queries - -| Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | -|------------------------------------------ |----------:|----------:|----------:|-------:|---------:|-------:|----------:| -| QueryWithInlineObjContext | 3.875 us | 0.0155 us | 0.0145 us | 1.00 | 0.00 | 1.0910 | 3.38 KB | -| QueryWithoutInlineObjContext | 4.515 us | 0.0215 us | 0.0191 us | 1.17 | 0.01 | 1.2894 | 3.97 KB | -| PlainQueryWithCachedPipeline | 8.352 us | 0.0367 us | 0.0325 us | 2.16 | 0.01 | 1.1139 | 3.42 KB | -| CachedQueryWithCachedPipeline | 8.460 us | 0.0512 us | 0.0454 us | 2.18 | 0.01 | 1.1139 | 3.42 KB | -| PlainQueryWithSecuredPipeline | 8.146 us | 0.0434 us | 0.0406 us | 2.10 | 0.01 | 1.7090 | 5.26 KB | -| SecuredQueryWithSecuredPipeline | 15.394 us | 0.1026 us | 0.0857 us | 3.97 | 0.03 | 2.2888 | 7.09 KB | -| SecuredQueryButFailingWithSecuredPipeline | 88.476 us | 0.3902 us | 0.3650 us | 22.83 | 0.12 | 2.5635 | 8.08 KB | - -## RemoteCQRS - -| Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | -|---------------------- |----------:|----------:|----------:|-------:|---------:|-------:|----------:| -| SimpleMiddleware | 5.355 us | 0.0219 us | 0.0194 us | 1.00 | 0.00 | 4.3488 | 13.38 KB | -| EmptyQuery | 18.149 us | 0.1448 us | 0.1354 us | 3.39 | 0.03 | 5.7678 | 17.82 KB | -| EmptyCommand | 19.994 us | 0.1045 us | 0.0977 us | 3.73 | 0.02 | 5.7983 | 17.85 KB | -| MultipleFieldsQuery | 22.004 us | 0.0804 us | 0.0713 us | 4.11 | 0.02 | 5.8594 | 18.03 KB | -| MultipleFieldsCommand | 22.356 us | 0.1065 us | 0.0996 us | 4.18 | 0.02 | 5.8289 | 17.95 KB | diff --git a/docs/benchmarks/2018-12-17_01_before_ctx_removal.md b/docs/benchmarks/2018-12-17_01_before_ctx_removal.md deleted file mode 100644 index 42438a740..000000000 --- a/docs/benchmarks/2018-12-17_01_before_ctx_removal.md +++ /dev/null @@ -1,55 +0,0 @@ -# 2018-12-17 Before object context removal - -``` ini - -BenchmarkDotNet=v0.11.3, OS=arch -Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.100 - [Host] : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT - Core : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT - -Job=Core Runtime=Core - -``` - -## Pipelines - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|--------------------- |------------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| Empty | 64.02 ns | 0.4003 ns | 0.3744 ns | 1.00 | 0.00 | 0.0228 | - | - | 72 B | -| SingleElement | 71.12 ns | 0.2857 ns | 0.2673 ns | 1.11 | 0.01 | 0.0228 | - | - | 72 B | -| EmptyAutofac | 1,003.64 ns | 3.5256 ns | 3.2978 ns | 15.68 | 0.08 | 0.4368 | - | - | 1376 B | -| SingleElementAutofac | 1,386.19 ns | 4.6067 ns | 4.3091 ns | 21.65 | 0.15 | 0.5856 | - | - | 1848 B | - -## InProcCQRS__Commands - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|-------------------------------------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| CommandWithoutInlineObjContext | 4.401 us | 0.0528 us | 0.0494 us | 1.00 | 0.00 | 1.2512 | - | - | 3.85 KB | -| PlainCommandWithSecuredPipeline | 9.079 us | 0.1130 us | 0.0944 us | 2.06 | 0.03 | 1.8616 | - | - | 5.73 KB | -| SecuredCommandWithSecuredPipeline | 16.692 us | 0.2616 us | 0.2569 us | 3.79 | 0.08 | 2.4414 | - | - | 7.57 KB | -| SecuredButFailingCommandWithSecuredPipeline | 16.879 us | 0.0895 us | 0.0699 us | 3.83 | 0.04 | 2.4719 | - | - | 7.64 KB | -| PlainCommandWithValidatedPipeline | 8.300 us | 0.1039 us | 0.0921 us | 1.89 | 0.03 | 2.1667 | - | - | 6.7 KB | -| ValidCommandWithValidatedPipeline | 11.514 us | 0.2253 us | 0.2108 us | 2.62 | 0.05 | 2.8687 | - | - | 8.84 KB | -| InvalidCommandWithValidatedPipeline | 13.030 us | 0.1787 us | 0.1672 us | 2.96 | 0.04 | 2.5787 | - | - | 7.94 KB | - -## InProcCQRS__Queries - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|------------------------------------------ |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| QueryWithoutInlineObjContext | 4.398 us | 0.0465 us | 0.0435 us | 1.00 | 0.00 | 1.2894 | - | - | 3.97 KB | -| PlainQueryWithCachedPipeline | 9.840 us | 0.2330 us | 0.2392 us | 2.24 | 0.05 | 1.2970 | - | - | 4.02 KB | -| CachedQueryWithCachedPipeline | 9.697 us | 0.0645 us | 0.0538 us | 2.20 | 0.03 | 1.2970 | - | - | 4.02 KB | -| PlainQueryWithSecuredPipeline | 9.247 us | 0.2247 us | 0.3150 us | 2.13 | 0.08 | 1.8921 | - | - | 5.85 KB | -| SecuredQueryWithSecuredPipeline | 17.138 us | 0.1004 us | 0.0890 us | 3.90 | 0.05 | 2.4719 | - | - | 7.69 KB | -| SecuredQueryButFailingWithSecuredPipeline | 90.416 us | 0.4015 us | 0.3756 us | 20.56 | 0.23 | 2.8076 | - | - | 8.67 KB | - -## RemoteCQRS - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|---------------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| SimpleMiddleware | 5.776 us | 0.0250 us | 0.0234 us | 1.00 | 0.00 | 4.3488 | - | - | 13.38 KB | -| EmptyQuery | 18.753 us | 0.1874 us | 0.1661 us | 3.25 | 0.03 | 5.7678 | - | - | 17.82 KB | -| EmptyCommand | 20.617 us | 0.0795 us | 0.0705 us | 3.57 | 0.02 | 5.7983 | - | - | 17.85 KB | -| MultipleFieldsQuery | 23.077 us | 0.1055 us | 0.0935 us | 4.00 | 0.02 | 5.8594 | - | - | 18.03 KB | -| MultipleFieldsCommand | 22.930 us | 0.2724 us | 0.2415 us | 3.97 | 0.05 | 5.8289 | - | - | 17.95 KB | diff --git a/docs/benchmarks/2018-12-17_02_after_ctx_removal.md b/docs/benchmarks/2018-12-17_02_after_ctx_removal.md deleted file mode 100644 index 1cb4e1d1d..000000000 --- a/docs/benchmarks/2018-12-17_02_after_ctx_removal.md +++ /dev/null @@ -1,55 +0,0 @@ -# 2018-12-17 After object context removal - -``` ini - -BenchmarkDotNet=v0.11.3, OS=arch -Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.100 - [Host] : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT - Core : .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT - -Job=Core Runtime=Core - -``` - -## Pipelines - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|--------------------- |------------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| Empty | 61.04 ns | 0.2912 ns | 0.2581 ns | 1.00 | 0.00 | 0.0228 | - | - | 72 B | -| SingleElement | 69.85 ns | 0.3217 ns | 0.3009 ns | 1.14 | 0.01 | 0.0229 | - | - | 72 B | -| EmptyAutofac | 1,006.09 ns | 3.8921 ns | 3.4503 ns | 16.48 | 0.08 | 0.4368 | - | - | 1376 B | -| SingleElementAutofac | 1,295.23 ns | 4.5842 ns | 4.2881 ns | 21.22 | 0.09 | 0.5856 | - | - | 1848 B | - -## InProcCQRS__Commands - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|-------------------------------------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| CommandWithoutInlineObjContext | 3.680 us | 0.0130 us | 0.0122 us | 1.00 | 0.00 | 1.0529 | - | - | 3.26 KB | -| PlainCommandWithSecuredPipeline | 8.132 us | 0.0516 us | 0.0457 us | 2.21 | 0.02 | 1.6632 | - | - | 5.14 KB | -| SecuredCommandWithSecuredPipeline | 15.814 us | 0.0779 us | 0.0729 us | 4.30 | 0.02 | 2.2583 | - | - | 6.98 KB | -| SecuredButFailingCommandWithSecuredPipeline | 14.972 us | 0.0722 us | 0.0640 us | 4.07 | 0.02 | 2.2888 | - | - | 7.05 KB | -| PlainCommandWithValidatedPipeline | 5.669 us | 0.0372 us | 0.0330 us | 1.54 | 0.01 | 1.6174 | - | - | 4.98 KB | -| ValidCommandWithValidatedPipeline | 6.188 us | 0.0293 us | 0.0274 us | 1.68 | 0.01 | 1.6174 | - | - | 4.98 KB | -| InvalidCommandWithValidatedPipeline | 5.949 us | 0.0346 us | 0.0324 us | 1.62 | 0.01 | 1.6174 | - | - | 4.98 KB | - -## InProcCQRS__Queries - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|------------------------------------------ |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| QueryWithoutInlineObjContext | 3.940 us | 0.0128 us | 0.0119 us | 1.00 | 0.00 | 1.0910 | - | - | 3.38 KB | -| PlainQueryWithCachedPipeline | 10.066 us | 0.0785 us | 0.0696 us | 2.56 | 0.02 | 1.3123 | - | - | 4.06 KB | -| CachedQueryWithCachedPipeline | 10.212 us | 0.0554 us | 0.0463 us | 2.59 | 0.01 | 1.3123 | - | - | 4.06 KB | -| PlainQueryWithSecuredPipeline | 8.509 us | 0.0638 us | 0.0597 us | 2.16 | 0.02 | 1.7090 | - | - | 5.26 KB | -| SecuredQueryWithSecuredPipeline | 15.963 us | 0.0709 us | 0.0628 us | 4.05 | 0.02 | 2.2888 | - | - | 7.09 KB | -| SecuredQueryButFailingWithSecuredPipeline | 89.585 us | 0.4519 us | 0.4227 us | 22.74 | 0.12 | 2.5635 | - | - | 8.05 KB | - -## RemoteCQRS - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | -|---------------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| -| SimpleMiddleware | 5.282 us | 0.0193 us | 0.0150 us | 1.00 | 0.00 | 4.3488 | - | - | 13.38 KB | -| EmptyQuery | 17.146 us | 0.0853 us | 0.0798 us | 3.24 | 0.02 | 5.5847 | - | - | 17.23 KB | -| EmptyCommand | 18.963 us | 0.1133 us | 0.1060 us | 3.60 | 0.02 | 5.6152 | - | - | 17.26 KB | -| MultipleFieldsQuery | 21.414 us | 0.0845 us | 0.0706 us | 4.05 | 0.01 | 5.6458 | - | - | 17.44 KB | -| MultipleFieldsCommand | 21.557 us | 0.1782 us | 0.1667 us | 4.09 | 0.02 | 5.6458 | - | - | 17.35 KB | diff --git a/docs/benchmarks/2019-12-30.md b/docs/benchmarks/2019-12-30.md deleted file mode 100644 index b47c18f2d..000000000 --- a/docs/benchmarks/2019-12-30.md +++ /dev/null @@ -1,55 +0,0 @@ -# 2019-12-30 - -``` ini - -BenchmarkDotNet=v0.12.0, OS=arch -Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.100 - [Host] : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT - Job-TLSLIE : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT - -Runtime=.NET Core 3.1 - -``` - -## Pipelines - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|--------------------- |------------:|---------:|---------:|------:|--------:|-------:|------:|------:|----------:| -| Empty | 50.37 ns | 1.029 ns | 1.409 ns | 1.00 | 0.00 | 0.0229 | - | - | 72 B | -| SingleElement | 59.96 ns | 0.409 ns | 0.363 ns | 1.17 | 0.03 | 0.0229 | - | - | 72 B | -| EmptyAutofac | 1,198.05 ns | 3.759 ns | 3.516 ns | 23.53 | 0.78 | 0.7133 | - | - | 2240 B | -| SingleElementAutofac | 1,448.58 ns | 6.232 ns | 5.829 ns | 28.44 | 0.88 | 0.8659 | - | - | 2720 B | - -## InProcCQRS__Commands - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------------------------- |----------:|----------:|----------:|------:|--------:|-------:|------:|------:|----------:| -| CommandWithoutInlineObjContext | 3.805 us | 0.0142 us | 0.0119 us | 1.00 | 0.00 | 1.3504 | - | - | 4.16 KB | -| PlainCommandWithSecuredPipeline | 6.776 us | 0.0488 us | 0.0432 us | 1.78 | 0.01 | 1.9226 | - | - | 5.89 KB | -| SecuredCommandWithSecuredPipeline | 13.570 us | 0.0649 us | 0.0575 us | 3.57 | 0.02 | 2.5177 | - | - | 7.72 KB | -| SecuredButFailingCommandWithSecuredPipeline | 14.593 us | 0.0702 us | 0.0657 us | 3.83 | 0.02 | 2.5330 | - | - | 7.79 KB | -| PlainCommandWithValidatedPipeline | 7.192 us | 0.0269 us | 0.0252 us | 1.89 | 0.01 | 2.2583 | - | - | 6.92 KB | -| ValidCommandWithValidatedPipeline | 10.129 us | 0.0481 us | 0.0450 us | 2.66 | 0.02 | 2.9449 | - | - | 9.04 KB | -| InvalidCommandWithValidatedPipeline | 10.623 us | 0.0574 us | 0.0509 us | 2.79 | 0.01 | 2.5787 | - | - | 7.9 KB | - -## InProcCQRS__Queries - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|------------------------------------------ |-----------:|----------:|----------:|------:|--------:|-------:|------:|------:|----------:| -| QueryWithoutInlineObjContext | 3.946 us | 0.0355 us | 0.0332 us | 1.00 | 0.00 | 1.3733 | - | - | 4.23 KB | -| PlainQueryWithCachedPipeline | 8.377 us | 0.0659 us | 0.0584 us | 2.13 | 0.03 | 1.5259 | - | - | 4.68 KB | -| CachedQueryWithCachedPipeline | 8.086 us | 0.0736 us | 0.0688 us | 2.05 | 0.02 | 1.5259 | - | - | 4.68 KB | -| PlainQueryWithSecuredPipeline | 6.942 us | 0.0409 us | 0.0363 us | 1.76 | 0.01 | 1.9455 | - | - | 5.96 KB | -| SecuredQueryWithSecuredPipeline | 13.859 us | 0.0285 us | 0.0238 us | 3.52 | 0.03 | 2.5330 | - | - | 7.79 KB | -| SecuredQueryButFailingWithSecuredPipeline | 102.082 us | 0.3615 us | 0.3381 us | 25.87 | 0.25 | 2.8076 | - | - | 8.67 KB | - -## RemoteCQRS - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -|---------------------- |-------------:|------------:|------------:|-------:|--------:|-------:|------:|------:|----------:| -| SimpleMiddleware | 2,706.3 ns | 14.71 ns | 13.04 ns | 1.00 | 0.00 | 0.5836 | - | - | 1.8 KB | -| EmptyQuery | 376,221.2 ns | 1,141.99 ns | 1,012.35 ns | 139.02 | 0.71 | 0.4883 | - | - | 1.93 KB | -| EmptyCommand | 876.2 ns | 4.93 ns | 4.62 ns | 0.32 | 0.00 | 0.4435 | - | - | 1.36 KB | -| MultipleFieldsQuery | 378,453.8 ns | 3,586.61 ns | 3,179.43 ns | 139.84 | 1.24 | 0.4883 | - | - | 1.96 KB | -| MultipleFieldsCommand | 881.6 ns | 4.08 ns | 3.62 ns | 0.33 | 0.00 | 0.4463 | - | - | 1.37 KB | diff --git a/docs/benchmarks/README.md b/docs/benchmarks/README.md deleted file mode 100644 index d058b344d..000000000 --- a/docs/benchmarks/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Benchmarks - - 1. [2018-07-25](./2018-07-25.md), - 2. [2018-12-17 Before ObjCtx removal](./2018-12-17_01_before_ctx_removal.md), - 3. [2018-12-17 After ObjCtx removal](./2018-12-17_02_after_ctx_removal.md), - 4. [2019-12-30](./2019-12-30.md). diff --git a/docs/domain/README.md b/docs/domain/README.md index 4ee002238..187fa5142 100644 --- a/docs/domain/README.md +++ b/docs/domain/README.md @@ -1,5 +1,6 @@ # Domain -1. [Value objects](../adrs/2021-02-04_value_object.md), -2. [IDs](./ids.md), -3. [Aggregates](./03_aggregates.md). +Here, we describe basic concepts of the DDD part of CoreLibrary. + +1. [IDs](./ids.md), +2. [Aggregates](./aggregates.md). diff --git a/docs/domain/03_aggregates.md b/docs/domain/aggregates.md similarity index 53% rename from docs/domain/03_aggregates.md rename to docs/domain/aggregates.md index 2de33c6e3..f7a8b7fe5 100644 --- a/docs/domain/03_aggregates.md +++ b/docs/domain/aggregates.md @@ -1,10 +1,10 @@ # Aggregates -Aggregates are part of DDD. To create one you have to create a class inheriting [`IAggregateRoot`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) or [`IAggregateRootWithoutOptimisticConcurrency`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs). +Aggregates are part of DDD. To create one you have to create a class inheriting [`IAggregateRoot`] or [`IAggregateRootWithoutOptimisticConcurrency`]. -## [`IAggregateRoot`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) +## [`IAggregateRoot`] -[`IAggregateRoot`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) requires you to specify Id type. [`IAggregateRoot`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) has `Id`, because aggregate root is entity and entities have Ids. +[`IAggregateRoot`] requires you to specify Id type. [`IAggregateRoot`] has `Id`, because aggregate root is entity and entities have Ids. Consider the following aggregate. @@ -36,15 +36,15 @@ Parameterless constructor is required by EntityFramework. It is private because ### Id -[`IAggregateRoot`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) requires you to specify identity type. Every aggregate has an `Id` property of the specified type. We recommend using source-generated Ids as Id types. +[`IAggregateRoot`] requires you to specify identity type. Every aggregate has an `Id` property of the specified type. We recommend using source-generated Ids as Id types. ### `IOptimisticConcurrency.DateModified` -`DateModified` is optimistic concurrency token managed by application code. It is managed by [`EFRepository`](../../src/Domain/LeanCode.DomainModels.EF/EFRepository.cs) and you shouldn't do it by yourself. It is written this way, instead of `public DateTime DateModified { get; set; }`, to make it not accessible outside [`EFRepository`](../../src/Domain/LeanCode.DomainModels.EF/EFRepository.cs). +`DateModified` is optimistic concurrency token managed by application code. It is managed by repositories (most commonly, [`EFRepository`]) and you shouldn't do it by yourself. It is written this way, instead of `public DateTime DateModified { get; set; }`, to make it not accessible outside [`EFRepository`]. -## [`IAggregateRootWithoutOptimisticConcurrency`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) +## [`IAggregateRootWithoutOptimisticConcurrency`] -If you don't want to use optimistic concurrency for your aggregate, you can use [`IAggregateRootWithoutOptimisticConcurrency`](../../src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs) instead. +If you don't want to use optimistic concurrency for your aggregate, you can use [`IAggregateRootWithoutOptimisticConcurrency`] instead. Consider aggregate from previous example, but without optimistic concurrency. @@ -67,4 +67,8 @@ public class User : IAggregateRootWithoutOptimisticConcurrency ## More -If you want to read more about writing aggregates, you can do that [here](../guides/01_creating_an_aggregate.md). +If you want to read more about writing aggregates, you can do that [here](../guides/basic/creating_an_aggregate.md). + +[`IAggregateRoot`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs +[`IAggregateRootWithoutOptimisticConcurrency`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs +[`EFRepository`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels.EF/EFRepository.cs diff --git a/docs/domain/ids.md b/docs/domain/ids.md index 3469f3a71..716d68ccd 100644 --- a/docs/domain/ids.md +++ b/docs/domain/ids.md @@ -2,10 +2,10 @@ The domain part of the library supports a set of generic IDs: -1. [`Id`](../../src/Domain/LeanCode.DomainModels/Model/Id.cs), -2. [`IId`](../../src/Domain/LeanCode.DomainModels/Model/Id.cs), -3. [`LId`](../../src/Domain/LeanCode.DomainModels/Model/Id.cs), -4. [`SId`](../../src/Domain/LeanCode.DomainModels/Model/SId.cs). +1. [`Id`], +2. [`IId`], +3. [`LId`], +4. [`SId`]. All the types give you type safety when passing the IDs, without introducing penalty (they basically work as `newtype`s). Unfortunately, they require an entity to be defined beforehand - it works as a generic parameter. This means you can't use it without a corresponding entity type. This poses a problem if you want to use the ID outside the parent domain. It is also quite hard to use - you need to know the exact ID format before you reference it (you need to choose between the four types when you just want to reference other entity). @@ -23,7 +23,7 @@ To use the source-generated IDs, you first need to reference the source generato ``` -Then, you need to add a partial struct record that will be filled up by the compiler when building the project, and decorate it with [`TypedIdAttribute`](../../src/Domain/LeanCode.DomainModels/Ids/TypedIdAttribute.cs): +Then, you need to add a partial struct record that will be filled up by the compiler when building the project, and decorate it with [`TypedIdAttribute`]: ```cs [TypedId(TypedIdFormat.RawInt)] @@ -83,3 +83,9 @@ public readonly partial record struct VeryLongUserId; // The `VeryLongUserId` will have format `user_(guid)`, with `New` using `Guid.NewGuid` as random source. ``` + +[`Id`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/Id.cs +[`IId`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/Id.cs +[`LId`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/Id.cs +[`SId`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Model/Id.cs +[`TypedIdAttribute`]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Domain/LeanCode.DomainModels/Ids/TypedIdAttribute.cs diff --git a/docs/general/README.md b/docs/general/README.md index 66fffb1c3..e04f61901 100644 --- a/docs/general/README.md +++ b/docs/general/README.md @@ -1,7 +1,7 @@ -# General +# Overview Here, we describe general topics regarding CoreLib: - 1. [Versioning](./01_versioning-and-support-matrix.md), - 2. [Building & testing](./02_building-and-testing.md), - 3. [Project structure](./03_project-structure.md). + 1. [Versioning](./versioning_and_support_matrix.md), + 2. [Building & testing](./building_and_testing.md), + 3. [Project structure](./project_structure.md). diff --git a/docs/general/02_building-and-testing.md b/docs/general/building_and_testing.md similarity index 100% rename from docs/general/02_building-and-testing.md rename to docs/general/building_and_testing.md diff --git a/docs/general/03_project-structure.md b/docs/general/project_structure.md similarity index 78% rename from docs/general/03_project-structure.md rename to docs/general/project_structure.md index 5ea43cffd..70f13eef6 100644 --- a/docs/general/03_project-structure.md +++ b/docs/general/project_structure.md @@ -26,11 +26,11 @@ The `src` folder that contains the main source code is then divided into: ## Build system -CoreLib build system mostly MSBuild-based, with some help of CI system to orchestrate build/test/publish process (see [Building & Testing](./02_building-and-testing.md) for more details). +CoreLib build system mostly MSBuild-based, with some help of CI system to orchestrate build/test/publish process (see [Building & Testing](./building_and_testing.md) for more details). We leverage .NET Core's MSBuild `Directory.Build.targets` files to centrally manage dependency versions. It is forbidden to directly specify `Version` in `csproj`s. Instead, one adds simple `` and then `` in `Directory.Build.targets` in the CoreLib root. This immensely helps avoiding dependency conflicts down the road. -Besides `.targets` file, we use central `Directory.Build.props` to manage some of the project properties. Check [/Directory.Build.props](../../Directory.Build.props), [/src/Directory.Build.props](../../src/Directory.Build.props) and [/test/Directory.Build.props](../../test/Directory.Build.props) what is being centrally set. +Besides `.targets` file, we use central `Directory.Build.props` to manage some of the project properties. Check [/Directory.Build.props], [/src/Directory.Build.props] and [/test/Directory.Build.props] what is being centrally set. ## Creating new packages @@ -48,3 +48,7 @@ Or you can just modify the following project template (most of the projects use ``` :) + +[/Directory.Build.props]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/Directory.Build.props +[/src/Directory.Build.props]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Directory.Build.props +[/test/Directory.Build.props]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/test/Directory.Build.props diff --git a/docs/general/01_versioning-and-support-matrix.md b/docs/general/versioning_and_support_matrix.md similarity index 100% rename from docs/general/01_versioning-and-support-matrix.md rename to docs/general/versioning_and_support_matrix.md diff --git a/docs/guides/00_intro.md b/docs/guides/00_intro.md deleted file mode 100644 index e5254ccd8..000000000 --- a/docs/guides/00_intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# Intro - -The following sections show some common real-life scenarios of the CoreLibrary usage. The code presented in the examples can be found in [src](./src/) directory. diff --git a/docs/guides/0X_authorization.md b/docs/guides/0X_authorization.md deleted file mode 100644 index 464090415..000000000 --- a/docs/guides/0X_authorization.md +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/docs/guides/0X_handling_events.md b/docs/guides/0X_handling_events.md deleted file mode 100644 index 464090415..000000000 --- a/docs/guides/0X_handling_events.md +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/docs/guides/0X_pipeline.md b/docs/guides/0X_pipeline.md deleted file mode 100644 index 464090415..000000000 --- a/docs/guides/0X_pipeline.md +++ /dev/null @@ -1 +0,0 @@ -# TODO diff --git a/docs/guides/README.md b/docs/guides/README.md new file mode 100644 index 000000000..7865a8d90 --- /dev/null +++ b/docs/guides/README.md @@ -0,0 +1,17 @@ +# Overview + +The following sections show some common real-life scenarios of the CoreLibrary usage. + +## Basic guides + +1. [How to create and Aggregate](./basic/creating_an_aggregate.md), +2. [Writing queries](./basic/writing_queries.md), +3. [Writing commands](./basic/writing_commands.md). + +## Features + +1. [Force Update](./features/force_update.md). + +## Advanced + +1. [v8 Migration Guide](./advanced/v8_migration_guide.md). diff --git a/docs/guides/0X_v8_migration_guide.md b/docs/guides/advanced/v8_migration_guide.md similarity index 97% rename from docs/guides/0X_v8_migration_guide.md rename to docs/guides/advanced/v8_migration_guide.md index aff648e6a..e5b640556 100644 --- a/docs/guides/0X_v8_migration_guide.md +++ b/docs/guides/advanced/v8_migration_guide.md @@ -2,11 +2,9 @@ ## Dependency injection & startup changes changes -Autofac is no longer the DI container of choice, corelibrary projects use `Microsoft.Extension.DependencyInjection`. -Autofac based `IAppModule`s are still available in `LeanCode.Components.Autofac` however no project depends on it. +Autofac is no longer the DI container of choice, corelibrary projects use `Microsoft.Extension.DependencyInjection`. Autofac based `IAppModule`s are still available in `LeanCode.Components.Autofac` however no project depends on it. -Instead services are registered via extension methods on `Microsoft.Extension.DependencyInjection.IServiceCollection`. -Typically an `XYZModule` class was replaced with `AddXYZ()` extension method (e.g. `AddSmsSender()` replaced `SmsSenderModule`) +Instead services are registered via extension methods on `Microsoft.Extension.DependencyInjection.IServiceCollection`. Typically an `XYZModule` class was replaced with `AddXYZ()` extension method (e.g. `AddSmsSender()` replaced `SmsSenderModule`) Additionally, modules requiring configuration now require passing it explicitly when registering services. E.g. previously adding `SmsSenderModule` required the client to additionally register `SmsApiConfiguration` class. Now, the configuration class is required when calling `AddSmsSender()` method. @@ -402,7 +400,6 @@ Similarly, you can override the rest of Kratos api clients, if your application .NET 8 introduced `System.TimeProvider` class. To avoid name clash we renamed `LeanCode.Time.TimeProvider` to `LeanCode.TimeProvider.Time` (the package name still is `LeanCode.TimeProvider`). The `Time` class is built on top of `System.TimeProvider`. `FixedTimeProvider` was replaced by `TestTimeProvider`, which is available in `LeanCode.TimeProvider.TestHelpers` package. - ```csharp // before using LeanCode.Time; @@ -462,7 +459,7 @@ public class Entity : IIdentifiable ### Ulids -[Ulid](https://github.com/ulid/spec) type was introduced (vendored implementation of https://github.com/Cysharp/Ulid). +[Ulid](https://github.com/ulid/spec) type was introduced (vendored implementation of ). Also, a source generated id type based on ulid was introduced (`TypedIdFormat.PrefixedUlid`). ### Azure Workload Identity diff --git a/docs/guides/basic/authorization.md b/docs/guides/basic/authorization.md new file mode 100644 index 000000000..009b2a048 --- /dev/null +++ b/docs/guides/basic/authorization.md @@ -0,0 +1,3 @@ +# Authorization + +TODO diff --git a/docs/guides/01_creating_an_aggregate.md b/docs/guides/basic/creating_an_aggregate.md similarity index 96% rename from docs/guides/01_creating_an_aggregate.md rename to docs/guides/basic/creating_an_aggregate.md index e4f92883e..eed2b17f5 100644 --- a/docs/guides/01_creating_an_aggregate.md +++ b/docs/guides/basic/creating_an_aggregate.md @@ -40,7 +40,7 @@ public class Project : IAggregateRoot } ``` -As you can see, the class implements `IAggregateRoot` interface - it marks the class as being the root of an aggregate. Moreover the `Id` field of the class is of type `ProjectId` - it is a special source-generated type present in the CoreLibrary. You can read more about `Id` types [here](../domain/ids.md). In this case, the Id of the Project will look somewhat like `project_45a8f39f-9df0-4a23-b781-2a46de22fac1`. +As you can see, the class implements `IAggregateRoot` interface - it marks the class as being the root of an aggregate. Moreover the `Id` field of the class is of type `ProjectId` - it is a special source-generated type present in the CoreLibrary. You can read more about `Id` types [here](../../domain/ids.md). In this case, the Id of the Project will look somewhat like `project_45a8f39f-9df0-4a23-b781-2a46de22fac1`. The `Project` also has a list of `Tasks`. Notice that there are two lists containing tasks of a project - the `Tasks` one is a readonly interface for the `tasks` which contents can be changed by the class. Generally, we try to model the API in such a way that the objects cannot be changed from the outside - an object's state should be modified only by the methods it exposes. Let's follow with a class representing a task belonging to a project: @@ -201,4 +201,4 @@ public class Project : IAggregateRoot } ``` -`UserAssignedToTask` has to implement `IDomainEvent` interface. After being raised, the event can be handled by the matching `IConsumer` to perform wanted action. To read more about events, see [handling events](./0X_handling_events). +`UserAssignedToTask` has to implement `IDomainEvent` interface. After being raised, the event can be handled by the matching `IConsumer` to perform wanted action. To read more about events, see [handling events](./handling_events.md). diff --git a/docs/guides/basic/handling_events.md b/docs/guides/basic/handling_events.md new file mode 100644 index 000000000..5c9486c80 --- /dev/null +++ b/docs/guides/basic/handling_events.md @@ -0,0 +1,3 @@ +# Handling events + +TODO diff --git a/docs/guides/basic/pipeline.md b/docs/guides/basic/pipeline.md new file mode 100644 index 000000000..dfae6f02b --- /dev/null +++ b/docs/guides/basic/pipeline.md @@ -0,0 +1,3 @@ +# Pipeline + +TODO diff --git a/docs/guides/02_writing_commands.md b/docs/guides/basic/writing_commands.md similarity index 89% rename from docs/guides/02_writing_commands.md rename to docs/guides/basic/writing_commands.md index f88ad8d61..35fa0fb32 100644 --- a/docs/guides/02_writing_commands.md +++ b/docs/guides/basic/writing_commands.md @@ -2,7 +2,7 @@ ## Intro -Interacting with systems that are based on the CoreLibrary follows the Command Query Responsibility Segregation ([CQRS](../basics/02_cqrs.md)) principle. As such, there are two main types of actions which can be performed in the system: commands, and [queries](./03_writing_queries.md) [^1]. In this guide we will focus on the former. A command is used to change the state of the system but it does not yield any results other then whether it succeeded — it can be treated as write-only action. +Interacting with systems that are based on the CoreLibrary follows the Command Query Responsibility Segregation ([CQRS](../../basics/cqrs.md)) principle. As such, there are two main types of actions which can be performed in the system: commands, and [queries](./writing_queries.md) [^1]. In this guide we will focus on the former. A command is used to change the state of the system but it does not yield any results other then whether it succeeded — it can be treated as write-only action. Each command consists of three parts: @@ -42,7 +42,7 @@ Notice the `ErrorCodes` class. These define possible validation errors which can Writing contracts in that way makes it possible for web and mobile clients to generate their own version of contract classes with use of the [Contract Generator](https://github.com/leancodepl/contractsgenerator), which is also a part of the CoreLibrary. -Also, a contract can have authorization attributes, which define rules that have to be satisfied for a client to be able to invoke the command. Authorization, however, is an extension to the contracts system and not an integral part of it. To learn more about authorizers, see [authorization](./0X_authorization.md). +Also, a contract can have authorization attributes, which define rules that have to be satisfied for a client to be able to invoke the command. Authorization, however, is an extension to the contracts system and not an integral part of it. To learn more about authorizers, see [authorization](./authorization.md). ## Command validator @@ -66,8 +66,8 @@ A validator for a command should inherit from `Validator` where `T` is a type ## Command handler -> **Info :information_source:** -> A command validator and handler are usually put in a single file with a `CH` suffix, e.g. `CreateProjectCH.cs`. +!!! note + A command validator and handler are usually put in a single file with a `CH` suffix, e.g. `CreateProjectCH.cs`. Finally, we arrive at a command handler that is the part which actually executes the command. For the `CreateProject` command, a handler may look like this: @@ -93,10 +93,10 @@ public class CreateProjectCH : ICommandHandler } ``` -> **Warning :warning:** -> According to DDD rules, a single command should only modify a single aggregate as an aggregate should be a transaction boundary in the system. If there is a need to modify multiple aggregates as a part of a single operation, raise events and modify all aggregates one by one in event handlers. +!!! warning + According to DDD rules, a single command should only modify a single aggregate as an aggregate should be a transaction boundary in the system. If there is a need to modify multiple aggregates as a part of a single operation, raise events and modify all aggregates one by one in event handlers. -The logic of the handler is straightforward — a new project with a given name is created and added to `projects` repository. The `IRepository` interface is a part of the CoreLibrary which allows to abstract the logic of persisting domain entities and to avoid dependency on the project's `DbContext` which allows for easier unit testing without having to mock the `DbContext`. A repository (and other services) should be injected into a command handler using dependency injection. Also, notice that the `Add` method used to persist newly created project is synchronous. It doesn't commit anything to the database which makes it possible to use [transactional outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern. The actual asynchronous operation in the database is performed by `StoreAndPublishEvents()` element in the pipeline, see [pipeline](./0X_pipeline.md) for more info. A basic repository for projects allowing for operations on database may look like this: +The logic of the handler is straightforward — a new project with a given name is created and added to `projects` repository. The `IRepository` interface is a part of the CoreLibrary which allows to abstract the logic of persisting domain entities and to avoid dependency on the project's `DbContext` which allows for easier unit testing without having to mock the `DbContext`. A repository (and other services) should be injected into a command handler using dependency injection. Also, notice that the `Add` method used to persist newly created project is synchronous. It doesn't commit anything to the database which makes it possible to use [transactional outbox](https://microservices.io/patterns/data/transactional-outbox.html) pattern. The actual asynchronous operation in the database is performed by `StoreAndPublishEvents()` element in the pipeline, see [pipeline](./pipeline.md) for more info. A basic repository for projects allowing for operations on database may look like this: ```csharp public class ProjectsRepository @@ -115,8 +115,8 @@ public class ProjectsRepository Here `EFRepository` is a class from the CoreLibrary containing default implementations for `Add`, `Delete` and `Update` methods which can be used to alter the contents of the database. Usually `FindAsync` method can be implemented as a simple retrieval of an entity based on Id but more complicated operations can also be done in the method if they are needed. Usually we don't wan to use `FindAsync` method of the corresponding `DbSet` to avoid concurrency errors resulting from entities being cached by the `DbSet`. -> **Warning :warning:** -> If an implemented command does not work, make sure that all referenced types are properly registered in the dependency injection container. +!!! warning + If an implemented command does not work, make sure that all referenced types are properly registered in the dependency injection container. ## More complex commands @@ -147,7 +147,7 @@ public class TaskDTO public class ErrorCodes { public const int NameCannotBeEmpty = 101; - public const int NameTooLong = 102; + public const int NameTooLong = 102; } } ``` diff --git a/docs/guides/03_writing_queries.md b/docs/guides/basic/writing_queries.md similarity index 96% rename from docs/guides/03_writing_queries.md rename to docs/guides/basic/writing_queries.md index 6d947efa1..1a1678186 100644 --- a/docs/guides/03_writing_queries.md +++ b/docs/guides/basic/writing_queries.md @@ -9,7 +9,7 @@ Each query consists of two parts: * Contract * Query handler -This is analogous to how [commands](./02_writing_commands.md) are implemented, except a validator. +This is analogous to how [commands](./writing_commands.md) are implemented, except a validator. ## Contract diff --git a/docs/guides/02_force_update.md b/docs/guides/features/force_update.md similarity index 58% rename from docs/guides/02_force_update.md rename to docs/guides/features/force_update.md index db6dc1556..f5ccac00c 100644 --- a/docs/guides/02_force_update.md +++ b/docs/guides/features/force_update.md @@ -1,5 +1,7 @@ # Force update +CoreLibrary provides an opinionated library for adding force update support to mobile apps. It connects to the broaded CoreLibrary ecosystem and has a ready-made [Flutter implementation](https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/force_update). + ## Configuration To enforce or suggest updates for client apps, you can utilize the `AddForceUpdate(...)` extension method from the `LeanCode.ForceUpdate` package in the `Startup.cs` file. This method is available on the `IServiceCollection` and needs to be called after `AddCQRS(...)`. The following example demonstrates the usage: @@ -22,6 +24,9 @@ public override void ConfigureServices(IServiceCollection services) ## Version support -After configuation above. [VersionSupport](../../src/Infrastructure/LeanCode.ForceUpdate.Contracts/VersionSupport.cs) query is created and available at `/cqrs/query/LeanCode.ForceUpdate.Contracts.VersionSupport`. The query takes `Platform` (either IOS or Android) and the `Version` of the client app as parameters. It returns whether the client's version is supported, currently supported version and minimum required version for specified provider. +After configuation above. [VersionSupport] query is created and available at `/cqrs/query/LeanCode.ForceUpdate.Contracts.VersionSupport`. The query takes `Platform` (either IOS or Android) and the `Version` of the client app as parameters. It returns whether the client's version is supported, currently supported version and minimum required version for specified provider. + +By default, if the client's version is below the minimum required version, the response will indicate that an update is needed. If the client's version is between minimum required and currently supported version, the response will suggest an update. If the app version is greater or equal to the currently supported version, the response will indicate that the app is up to date. It's also possible to change this behavior by creating custom version handler and overriding `CheckVersionAsync` method from the [VersionHandler] class, responsible for version checking. -By default, if the client's version is below the minimum required version, the response will indicate that an update is needed. If the client's version is between minimum required and currently supported version, the response will suggest an update. If the app version is greater or equal to the currently supported version, the response will indicate that the app is up to date. It's also possible to change this behavior by creating custom version handler and overriding `CheckVersionAsync` method from the [VersionHandler](../../src/Infrastructure/LeanCode.ForceUpdate/LeanCode.ForceUpdate.Services/VersionHandler.cs) class, responsible for version checking. +[VersionSupport]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.ForceUpdate.Contracts/VersionSupport.cs +[VersionHandler]: https://github.com/leancodepl/corelibrary/blob/v8.0-preview/src/Infrastructure/LeanCode.ForceUpdate/LeanCode.ForceUpdate.Services/VersionHandler.cs diff --git a/docs/guides/src/CQRS/AddTasksToProjectCH.cs b/docs/guides/src/CQRS/AddTasksToProjectCH.cs deleted file mode 100644 index 95945b6a9..000000000 --- a/docs/guides/src/CQRS/AddTasksToProjectCH.cs +++ /dev/null @@ -1,65 +0,0 @@ -public class AddTasksToProjectCV : ContextualValidator -{ - public AddTasksToProjectCV() - { - RuleFor(cmd => cmd.Tasks) - .NotNull() - .WithCode(AddTasksToProject.ErrorCodes.TasksCannotBeNull) - .NotEmpty() - .WithCode(AddTasksToProject.ErrorCodes.TasksCannotBeEmpty); - - RuleForEach(cmd => cmd.Tasks).ChildRules(child => child.RuleFor(c => c).SetValidator(new TaskDTOValidator())); - - RuleForAsync(cmd => cmd, CheckProjectExistsAsync) - .Equal(true) - .WithCode(AddContactToSite.ErrorCodes.ProjectDoesNotExist) - .WithMessage("A project with given Id does not exist."); - } - - private Task CheckProjectExistsAsync(IValidationContext ctx, AddTasksToProject command) - { - if (!SId.TryParse(command.ProjectId, out var projectId)) - { - return false; - } - - var appContext = ctx.AppContext(); - var dbContext = ctx.GetService(); - - return dbContext.Projects.AnyAsync(p => p.Id == projectId, appContext.CancellationToken); - } -} - -public class TaskDTOValidator : AbstractValidator -{ - public TaskDTOValidator() - { - RuleFor(dto => dto.Name) - .NotEmpty() - .WithCode(TaskDTO.ErrorCodes.NameCannotBeEmpty) - .MaximumLength(500) - .WithCode(TaskDTO.ErrorCodes.NameTooLong); - } -} - -public class AddTasksToProjectCH : ICommandHandler -{ - private readonly Serilog.ILogger logger = Serilog.Log.ForContext(); - - private readonly IRepository> projects; - - public AddTasksToProjectCH(IRepository> projects) - { - this.projects = projects; - } - - public async Task ExecuteAsync(CoreContext context, AddTasksToProject command) - { - var project = await projects.FindAndEnsureExistsAsync(command.ProjectId, context.CancellationToken); - project.AddTasks(command.Tasks.Select(t => Task.Create(t.Name))); - - projects.Update(project); - - logger.Information("{TaskCount} tasks added to project {ProjectId}.", command.Tasks.Count, project.Id); - } -} diff --git a/docs/guides/src/CQRS/AllProjectsQH.cs b/docs/guides/src/CQRS/AllProjectsQH.cs deleted file mode 100644 index aba456301..000000000 --- a/docs/guides/src/CQRS/AllProjectsQH.cs +++ /dev/null @@ -1,18 +0,0 @@ -public class AllProjectsQH : IQueryHandler> -{ - private readonly CoreDbContext dbContext; - - public AllProjectsQH(CoreDbContext dbContext) - { - this.dbContext = dbContext; - } - - public async Task> ExecuteAsync(CoreContext context, AllProjects query) - { - var q = dbContext.Projects.Select(p => new ProjectDTO { Id = p.Id, Name = p.Name, }); - - q = query.SortByNameDescending ? q.OrderByDescending(p => p.Name) : q.OrderBy(p => p.Name); - - return q.ToListAsync(context.CancellationToken); - } -} diff --git a/docs/guides/src/CQRS/CreateProjectCH.cs b/docs/guides/src/CQRS/CreateProjectCH.cs deleted file mode 100644 index 0b56bc436..000000000 --- a/docs/guides/src/CQRS/CreateProjectCH.cs +++ /dev/null @@ -1,31 +0,0 @@ -public class CreateProjectCV : ContextualValidator -{ - public CreateProjectCV() - { - RuleFor(cmd => cmd.Name) - .NotEmpty() - .WithCode(CreateProject.ErrorCodes.NameCannotBeEmpty) - .MaximumLength(500) - .WithCode(CreateProject.ErrorCodes.NameTooLong); - } -} - -public class CreateProjectCH : ICommandHandler -{ - private readonly Serilog.ILogger logger = Serilog.Log.ForContext(); - - private readonly IRepository> projects; - - public CreateProjectCH(IRepository> projects) - { - this.projects = projects; - } - - public Task ExecuteAsync(CoreContext context, CreateProject command) - { - var project = Project.Create(command.Name); - projects.Add(project); - - logger.Information("Project {ProjectId} added", project.Id); - } -} diff --git a/docs/guides/src/Contracts/AddTasksToProject.cs b/docs/guides/src/Contracts/AddTasksToProject.cs deleted file mode 100644 index 99e087995..000000000 --- a/docs/guides/src/Contracts/AddTasksToProject.cs +++ /dev/null @@ -1,24 +0,0 @@ -[AllowUnauthorized] -public class AddTasksToProject : ICommand -{ - public string ProjectId { get; set; } - public List Tasks { get; set; } - - public static class ErrorCodes - { - public const int ProjectDoesNotExist = 1; - public const int TasksCannotBeNull = 2; - public const int TasksCannotBeEmpty = 3; - } -} - -public class TaskDTO -{ - public string Name { get; set; } - - public class ErrorCodes - { - public const int NameCannotBeEmpty = 101; - public const int NameTooLong = 102; - } -} diff --git a/docs/guides/src/Contracts/AllProjects.cs b/docs/guides/src/Contracts/AllProjects.cs deleted file mode 100644 index 2f3c4558d..000000000 --- a/docs/guides/src/Contracts/AllProjects.cs +++ /dev/null @@ -1,11 +0,0 @@ -[AllowUnauthorized] -public class AllProjects : IQuery> -{ - public bool SortByNameDescending { get; set; } -} - -public class ProjectDTO -{ - public string Id { get; set; } - public string Name { get; set; } -} diff --git a/docs/guides/src/Contracts/CreateProject.cs b/docs/guides/src/Contracts/CreateProject.cs deleted file mode 100644 index c5387b97b..000000000 --- a/docs/guides/src/Contracts/CreateProject.cs +++ /dev/null @@ -1,11 +0,0 @@ -[AllowUnauthorized] -public class CreateProject : ICommand -{ - public string Name { get; set; } - - public static class ErrorCodes - { - public const int NameCannotBeEmpty = 1; - public const int NameTooLong = 2; - } -} diff --git a/docs/guides/src/DataAccess/ProjectsRepository.cs b/docs/guides/src/DataAccess/ProjectsRepository.cs deleted file mode 100644 index 675fb7e3a..000000000 --- a/docs/guides/src/DataAccess/ProjectsRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -public class ProjectsRepository : EFRepository, TContext> - where TContext : DbContext -{ - public ProjectsRepository(TContext dbContext) - : base(dbContext) { } - - public override Task FindAsync(SId id, CancellationToken cancellationToken = default) - { - return DbSet.AsTracking().FirstOrDefaultAsync(p => p.Id == id, cancellationToken)!; - } -} diff --git a/docs/guides/src/Domain/Project.cs b/docs/guides/src/Domain/Project.cs deleted file mode 100644 index 9459e16e8..000000000 --- a/docs/guides/src/Domain/Project.cs +++ /dev/null @@ -1,44 +0,0 @@ -public class Project : IAggregateRoot> -{ - private readonly List tasks = new(); - - public SId Id { get; private init; } - public string Name { get; private set; } - - public IReadOnlyList Tasks => tasks; - - DateTime IOptimisticConcurrency.DateModified { get; set; } - - private Project() { } - - public static Project Create(string name) - { - return new Project { Id = SId.New(), Name = name, }; - } - - public void AddTasks(IEnumerable taskNames) - { - this.tasks.AddRange(taskNames.Select(tn => Task.Create(this, tn))); - } - - public void EditTask(SId taskId, string name) - { - tasks.Single(t => t.Id == taskId).Edit(name); - } - - public void AssignUserToTask(SId taskId, SId userId) - { - tasks.Single(t => t.Id == taskId).AssignUser(userId); - DomainEvents.Raise(new UserAssignedToTask(SId < Task > taskId, SId < User > userId)); - } - - public void UnassignUserFromTask(SId taskId) - { - tasks.Single(t => t.Id == taskId).UnassignUser(userId); - } - - public void ChangeTaskStatus(SId taskId, TaskStatus status) - { - tasks.Single(t => t.Id == taskId).ChangeTaskStatus(status); - } -} diff --git a/docs/guides/src/Domain/Task.cs b/docs/guides/src/Domain/Task.cs deleted file mode 100644 index 5fecc1eb7..000000000 --- a/docs/guides/src/Domain/Task.cs +++ /dev/null @@ -1,50 +0,0 @@ -public class Task : IIdentifiable> -{ - public SId Id { get; private init; } - public string Name { get; private set; } - public TaskStatus Status { get; private set; } - public SId? AssignedUser { get; private set; } - - public Project ParentProject { get; private init; } - - DateTime IOptimisticConcurrency.DateModified { get; set; } - - private Task() { } - - public static Task Create(Project parentProject, string name) - { - return new Task - { - Name = name, - ParentProject = parentProject, - Status = TaskStatus.NotStarted, - }; - } - - public void Edit(string name) - { - Name = name; - } - - public void AssignUser(SId userId) - { - AssignedUser = userId; - } - - public void UnassignUser() - { - AssignedUser = null; - } - - public void ChangeStatus(TaskStatus status) - { - Status = status; - } - - public enum TaskStatus - { - NotStarted, - InProgress, - Finished, - } -} diff --git a/docs/guides/src/Domain/User.cs b/docs/guides/src/Domain/User.cs deleted file mode 100644 index 5edbe36f9..000000000 --- a/docs/guides/src/Domain/User.cs +++ /dev/null @@ -1,20 +0,0 @@ -public class User : IAggregateRoot> -{ - public SId Id { get; private init; } - public string Name { get; private set; } - public string Email { get; private set; } - - DateTime IOptimisticConcurrency.DateModified { get; set; } - - private User() { } - - public static User Create(string name, string email) - { - return new User - { - Id = SId.New(), - Name = name, - Email = email, - }; - } -} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..99e5e66b7 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,43 @@ +site_name: LeanCode CoreLibrary +repo_url: https://github.com/leancodepl/corelibrary +docs_dir: ./docs +theme: + name: material + features: + - navigation.indexes +markdown_extensions: + - admonition + - footnotes +nav: + - Overview: README.md + - General: + - ./general/README.md + - ./general/building_and_testing.md + - ./general/project_structure.md + - ./general/versioning_and_support_matrix.md + - Basics: + - ./basics/README.md + - ./basics/app.md + - ./basics/cqrs.md + - ./basics/mass_transit_integration.md + - Domain Concepts: + - ./domain/README.md + - ./domain/ids.md + - ./domain/aggregates.md + - Guides: + - Overview: ./guides/README.md + - Basics: + - ./guides/basic/creating_an_aggregate.md + - ./guides/basic/writing_commands.md + - ./guides/basic/writing_queries.md + - ./guides/basic/authorization.md + - ./guides/basic/handling_events.md + - ./guides/basic/pipeline.md + - Features: + - ./guides/features/force_update.md + - Advanced: + - ./guides/advanced/v8_migration_guide.md + - ADRs: + - Overview: ./adrs/README.md + - Value Objects: ./adrs/2021-02-04_value_object.md + - Optimistic Concurrency: ./adrs/2021-11-25_optimistic_concurrency.md