Vogen Help

Overview

cavey-logo.png

Vogen wraps .NET primitives. You provide this:

[ValueObject<int>] public partial struct CustomerId { }

... and Vogen generates source code similar to this:

public partial struct CustomerId : System.IEquatable<CustomerId>, System.IComparable<CustomerId>, System.IComparable { private readonly int _value; public readonly int Value => _value; public CustomerId() { throw new ValueObjectValidationException("Validation skipped"); } private CustomerId(int value) => _value = value; public static CustomerId From(int value) { CustomerId instance = new CustomerId(value); return instance; } public readonly bool Equals(CustomerId other) ... public readonly bool Equals(int primitive) ... public readonly override bool Equals(object obj) ... public static bool operator ==(CustomerId left, CustomerId right) public static bool operator !=(CustomerId left, CustomerId right) public static bool operator ==(CustomerId left, int right) ... public static bool operator !=(CustomerId left, int right) ... public static bool operator ==(int left, CustomerId right) ... public static bool operator !=(int left, CustomerId right) ... public readonly override int GetHashCode() ... public readonly override string ToString() ... }

Now, in your domain, you can use CustomerId instead of int with the confidence that it is valid and represents exactly what it says:

CustomerId customerId = CustomerId.From(123); SendInvoice(customerId); ... public void SendInvoice(CustomerId customerId) { ... }

The main goal of Vogen is to ensure the validity of your Value Objects.

It does this with code analyzers that add constraints to C#. The analyzers spot situations where you might end up with uninitialized Value Objects. These analyzers, by default, cause compilation errors.

An example violation is where you attempt to add a constructor to your Value Object. In the types tha that Vogen creates, there is one, and only one, way of creating instances, and that is with generated From method. Relying on user-provided constructors could mean that you forget to set or validate the value. Vogen spots when you create your own constructors and causes a compilation error:

[ValueObject] public partial struct CustomerId { // error CS0111: Type 'CustomerId' already defines a member called // 'CustomerId' with the same parameter type // That's because Vogen deliberately generates this // so that you can't create your own. public CustomerId() { } // error VOG008: Cannot have user defined constructors, please use // the From method for creation. public CustomerId(int value) { } }

Here's some of the issues that Vogen will spot when creating or consuming Value Objects:

// --- catches object creation expressions // error VOG010: Type 'CustomerId' cannot be constructed // with 'new' as it is prohibited var c = new CustomerId(); // error VOG009: ... cannot be constructed with default ... CustomerId c = default; // error VOG009: ... cannot be constructed with default ... var c = default(CustomerId); // error VOG010: ... cannot be constructed with 'new' ... var c = GetCustomerId(); // error VOG025: Type 'CustomerId' cannot be constructed via Reflection var c = Activator.CreateInstance<CustomerId>(); // error VOG025: Type 'MyVo' cannot be constructed via Reflection var c = Activator.CreateInstance(typeof(CustomerId)); // catches lambda expressions, e.g. // error VOG009: Type 'CustomerId' cannot be constructed with default Func<CustomerId> f = () => default; // --- catches method / local function return expressions, e.g. // error VOG009: Type 'CustomerId' cannot be constructed with default CustomerId GetCustomerId() => default; // error VOG010: Type 'CustomerId' cannot be constructed with 'new' CustomerId GetCustomerId() => new CustomerId(); // error VOG010: Type 'CustomerId' cannot be constructed with 'new' CustomerId GetCustomerId() => new(); // --- catches argument / parameter expressions // error VOG010: Type 'CustomerId' cannot be constructed with 'new' Task<CustomerId> t = Task.FromResult<CustomerId>(new()); // error VOG009: Type 'CustomerId' cannot be constructed with default void Process(CustomerId customerId = default) { }

One of the main goals of Vogen is to achieve almost the same speed and memory performance as using primitives directly. Put another way, if your decimal primitive represents an Account Balance, then there is extremely low overhead of using an AccountBalance Value Object instead. Please see the performance metrics for more information.

How this documentation is organized

  • These top level pages discuss key topics and concepts at a fairly high level and provide useful background information and explanation.

  • Tutorials—take you by the hand through a series of steps to create an application that uses Vogen. Start here if you're new to Vogen.

  • Reference guides-contains technical reference for Vogen usage, describing how it works and how to use it. The Assumption is that you have a basic understanding of key concepts.

  • How-to guides—recipes guiding you through the steps involved in addressing key problems and use-cases. They are more advanced than tutorials, and they assume some knowledge of how Vogen works.

Attribution

I took inspiration from Andrew Lock's StronglyTypedId and got some great ideas from Gérald Barré's Meziantou.Analyzer

Last modified: 22 November 2024