From e6185f8555072b9965e50dc3c0b937ca531074a8 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Mon, 25 Apr 2022 06:20:13 +0100 Subject: [PATCH] Updated documentation --- README.md | 82 ++++++++++++++++++++++++------------ docs/img/20220425061514.png | Bin 0 -> 5152 bytes docs/img/20220425061733.png | Bin 0 -> 10172 bytes 3 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 docs/img/20220425061514.png create mode 100644 docs/img/20220425061733.png diff --git a/README.md b/README.md index 0fd1f1698e..ead99e37fd 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ | :--- | :--- | :--- | :--- | | GitHub Actions | Ubuntu, Mac & Windows | ![Build](https://github.com/stevedunn/vogen/actions/workflows/build.yaml/badge.svg) | [![GitHub Actions Build History](https://buildstats.info/github/chart/SteveDunn/Vogen?branch=main&includeBuildsFromPullRequest=false)](https://github.com/SteveDunn/Vogen/actions) | - - [![GitHub release](https://img.shields.io/github/release/stevedunn/vogen.svg)](https://GitHub.com/stevedunn/vogen/releases/) [![GitHub license](https://img.shields.io/github/license/stevedunn/vogen.svg)](https://github.com/SteveDunn/Vogen/blob/main/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/Naereen/StrapDown.js.svg)](https://GitHub.com/stevedunn/vogen/issues/) [![GitHub issues-closed](https://img.shields.io/github/issues-closed/Naereen/StrapDown.js.svg)](https://GitHub.com/stevedunn/vogen/issues?q=is%3Aissue+is%3Aclosed) [![NuGet Badge](https://buildstats.info/nuget/Vogen)](https://www.nuget.org/packages/Vogen/) @@ -16,7 +14,7 @@ ## Overview -This is a .NET source generator and code analyzer for .NET. It's compatible with .NET Framework 4.6.2 and onwards, and .NET 5+. For .NET Framework, please see the FAQ section on how to get set up correctly. +This is a .NET source generator and code analyzer for .NET. It's compatible with .NET Framework 4.6.1 and onwards, and .NET 5+. For .NET Framework, please see the FAQ section on how to get set up correctly. The generator generates strongly typed **domain ideas**. You provide this: @@ -232,13 +230,14 @@ public readonly partial struct Celsius { ## Configuration -Each Value Object can have it's own _optional_ configuration. Configuration includes: +Each Value Object can have it's own *optional* configuration. Configuration includes: * The underlying type * Any 'conversions' (Dapper, System.Text.Json, Newtonsoft.Json, etc.) * The type of the exception that is thrown when validation fails If any of those above are not specified, then global configuration is inferred. It looks like this: + ```csharp [assembly: VogenDefaults(underlyingType: typeof(int), conversions: Conversions.None, throws: typeof(ValueObjectValidationException))] ``` @@ -260,7 +259,7 @@ There are several code analysis warnings for invalid configuration, including: (to run these yourself: `dotnet run -c Release -- --job short --framework net6.0 --filter *` in the `benchmarks` folders) As mentioned previously, the goal of Vogen is to achieve very similar performance compare to using primitives themselves. -Here's a benchmark comparing the use of a validated value object with underlying type of int vs using an int natively (_primitively_ 🤓) +Here's a benchmark comparing the use of a validated value object with underlying type of int vs using an int natively (*primitively* 🤓) ``` ini BenchmarkDotNet=v0.12.1, OS=Windows 10.0.22000 @@ -273,12 +272,12 @@ Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` + | Method | Mean | Error | StdDev | Ratio | Allocated | |------------------------ |---------:|---------:|---------:|------:|----------:| | UsingIntNatively | 13.57 ns | 0.086 ns | 0.005 ns | 1.00 | - | | UsingValueObjectStruct | 14.08 ns | 1.131 ns | 0.062 ns | 1.04 | - | - There is no discernable difference between using a native int and a VO struct; both are pretty much the same in terms of speed and memory. The next most common scenario is using a VO class to represent a native `String`. These results are: @@ -294,6 +293,7 @@ Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated | |------------------------- |---------:|----------:|---------:|------:|--------:|-------:|----------:| | UsingStringNatively | 135.4 ns | 16.89 ns | 0.93 ns | 1.00 | 0.00 | 0.0153 | 256 B | @@ -302,10 +302,13 @@ WarmupCount=3 There is a tiny amount of performance overhead, but these measurements are incredibly small. There is no memory overhead. ## Serialisation and type conversion + By default, each VO is decoarated with a `TypeConverter` and `System.Text.Json` (STJ) serializer. There are other converters/serialiazer for: + * Newtonsoft.Json (NSJ) * Dapper * EFCore +* [LINQ to DB](https://github.com/linq2db/linq2db) They are controlled by the `Conversions` enum. The following has serializers for NSJ and STJ: @@ -329,10 +332,12 @@ See the examples folder for more information. ## FAQ ### What versions of .NET are supported? -.NET Framework 4.6.2 and onwards and .NET 5+ are supported. + +.NET Framework 4.6.1 and onwards and .NET 5+ are supported. If you're using .NET Framework and the old style C# projects (the one before the 'SDK style' projects), then you'll need to do a few things differently: * add the reference using `PackageReference` in the .csproj file: + ```xml @@ -348,6 +353,38 @@ If you're using .NET Framework and the old style C# projects (the one before the ``` +### Why are they called 'Value Objects'? + +The term Value Object represents a small object whos equality is based on value and not identity. From [Wikipedia](https://en.wikipedia.org/wiki/Value_object) + +> _In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object._ + +In DDD, a Value Object is (again, from [Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks)) + +> _... a value object is an immutable object that contains attributes but has no conceptual identity_ + +### How can I view the code that is generated? + +Add this to your `.csproj` file: + +```xml + + true + Generated + + + + + +``` + +Then, you can view the generated files in the `Generated` folder. In Visual Studio, you need to select 'Show all files' in the Solution Explorer window: +![the solution explorer window showing the 'show all files' option](/docs/img/20220425061514.png) + +Here's an example from the included `Samples` project: + +![the solution explorer window showing generated files](docs/img/20220425061733.png) + ### Why can't I just use `public record struct CustomerId(int Value);`? That doesn't give you validation. To validate `Value`, you can't use the shorthand syntax (Primary Constructor). So you'd need to do: @@ -376,30 +413,32 @@ var c2 = default(CustomerId); ### Can I serialize and deserialize them? Yes. By default, each VO is decoarated with a `TypeConverter` and `System.Text.Json` (STJ) serializer. There are other converters/serialiazer for: + * Newtonsoft.Json (NSJ) * Dapper * EFCore +* LINQ to DB -### It seems like a lot of overhead; I can validate the value myself +### It seems like a lot of overhead; I can validate the value myself when I use it! You could, but to ensure consistency throughout your domain, you'd have to **validate everywhere**. And Shallow's Law says that that's not possible: > ⚖️ **Shalloway's Law** -_"when N things need to change and N > 1, Shalloway will find at most N - 1 of these things."_ +> *"when N things need to change and N > 1, Shalloway will find at most N - 1 of these things."* -Concretely: _"When 5 things need to change, Shalloway will find at most, 4 of these things."_ +Concretely: *"When 5 things need to change, Shalloway will find at most, 4 of these things."* -### If my VO is a `struct`, can I stop the use of `CustomerId customerId = default(CustomerId);`? +### If my VO is a `struct`, can I prohibit the use of `CustomerId customerId = default(CustomerId);`? -Yes. The analyser generates a compilation error. +**Yes**. The analyzer generates a compilation error. -### If my VO is a `struct`, can I stop the use of `CustomerId customerId = new(CustomerId);`? +### If my VO is a `struct`, can I prohibit the use of `CustomerId customerId = new(CustomerId);`? -Yes. The analyser generates a compilation error. +**Yes**. The analyzer generates a compilation error. ### If my VO is a struct, can I have my own constructor? -No. The parameter-less constructor is generated automatically, and the constructor that takes the underlying value is also generated automatically. +**No**. The parameter-less constructor is generated automatically, and the constructor that takes the underlying value is also generated automatically. If you add further constructors, then you will get a compilation error from the code generator, e.g. @@ -417,7 +456,7 @@ public partial struct CustomerId { ### If my VO is a struct, can I have my own fields? -You could, but you'd get compiler warning [CS0282-There is no defined ordering between fields in multiple declarations of partial class or struct 'type'](https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0282) +You *could*, but you'd get compiler warning [CS0282-There is no defined ordering between fields in multiple declarations of partial class or struct 'type'](https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0282) ### Why are there no implicit conversions to and from the primitive types that are being wrapped? @@ -469,7 +508,7 @@ var age10 = age20 / 2; age10 -= 12; // bang - goes negative?? ``` -But no.. The implicit cast in `var age10 = age20 / 2` results in an `int` and not an `Age`. Changing it to `Age age10 = age20 / 2` fixes it. But this does go to show that it can be confusing. +But no... the implicit cast in `var age10 = age20 / 2` results in an `int` and not an `Age`. Changing it to `Age age10 = age20 / 2` fixes it. But this does go to show that it can be confusing. ### Why is there no interface? @@ -493,15 +532,6 @@ So, callers could mess things up by calling `DoSomething(productId, supplierId, There would also be no need to know if it's validated, as, if it's in your domain, it's valid (there's no way to manually create invalid instances). And with that said, there'd also be no point in exposing the 'Validate' method via the interface because validation is done at creation. -### Why are they called 'Value Objects'? -The term Value Object represents a small object whos equality is based on value and not identity. From [Wikipedia](https://en.wikipedia.org/wiki/Value_object) - -> _In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object._ - -In DDD, a Value Object is (again, from [Wikipedia](https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks)) - -> _... a value object is an immutable object that contains attributes but has no conceptual identity_ - ### Can I represent special values Yes. You might want to represent special values for things like invalid or unspecified instances, e.g. diff --git a/docs/img/20220425061514.png b/docs/img/20220425061514.png new file mode 100644 index 0000000000000000000000000000000000000000..71179712139621514b9deb0d21d91deafcbe834b GIT binary patch literal 5152 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 O0000-4<< literal 0 HcmV?d00001 diff --git a/docs/img/20220425061733.png b/docs/img/20220425061733.png new file mode 100644 index 0000000000000000000000000000000000000000..37a3327900286cd0e242d0b19b47f89bc1701701 GIT binary patch literal 10172 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 literal 0 HcmV?d00001