ValueOf
ValueOf copied to clipboard
Bridge Pattern for handling ToString formatting and Validations
I'm super interest in this project, and there is some great stuff here - the simplicity is amazing.
Two things that jump out at me. First is the Validation flow. Validation is obviously super important to ValueObjects to ensure correctness, however the examples show throwing exceptions, which makes sense when provided void Validate()
as signature.
Has anyone looked at possible providing an interface to allow a different approach to handling validation errors - or even better an approach using OneOf
?
Second thing that stands out is the ToString, I love that it can be overrided to allow you to better display your data, I was wondering if there was an overload available using the IFormatter to allow reusability and culture specific formatting.
Interestingly these two could follow the same the strand, using the bridge pattern in order to surface more interopability.
I do guess however, that making the validation change, it would be harder to stop people using an invalid ValueObject because now it won't throw if something is wrong.
Finally are there any extension libraries to help users serialise these valueobjects to JSON, by overriding the jsonserialiser?
If there are some reasoning behind choosing the above approach, it may be worth adding them to the Readme - which I'm happy to do.
I started something similar to this library a few years back and recently put it on GitHub and NuGet. It started out life with a few key differences, namely:
- validation was compulsory - a delegate had to be provided in the constructor of each value object for validation
- the validation delegate returned a string saying what was wrong with the instance (or empty if it was OK) - the base class then threw an exception (to stop people accidentally forgetting to throw)
- there was no way to create a default instance (thereby allowing a potentially invalid value creeping in)
- it was serialisable via Newtonsoft.Json
- it just represented a single underlying type (
int
,decimal
,string
etc.)
I introduced this to where I'm working and the feedback was not to use delegates in the constructor, instead provide a Validate
method. The down-side to this is that (without some slow reflection), it's impossible to stop people using default constructors (new Age();
instead of Age.From(42)
). So that's the version I have in GitHub, which is now very similar to this. It also persists via System.Text.Json too.
I've been searching for a way to make this bullet-proof so that it's impossible to create a valueobject bypassing validation, but I'm coming to the conclusion that it's not possible.
I'm also coming to the conclusion that codegen is probably the best way to fulfill these requirements.
I'm also coming to the conclusion that codegen is probably the best way to fulfill these requirements.
Yes I have been thinking about that as well...
validation was compulsory - a delegate had to be provided in the constructor of each value object for validation
I actually really like this idea... any more reasons it was rejected?
I keep meaning to change the Validate method to something like protected override Exception Validate()
where returning null would indicate that validation was successful. This would let us add a bool TryFrom(out TType X) method.
In fact I'm always surprised no one has requested this, as throwing an exception on creation is horrible. I reckon it means no one uses the Validate methods 🤷♂️
On Mon, 6 Sep 2021, 09:29 Callum Linington, @.***> wrote:
I'm also coming to the conclusion that codegen is probably the best way to fulfill these requirements.
Yes I have been thinking about that as well...
validation was compulsory - a delegate had to be provided in the constructor of each value object for validation
I actually really like this idea... any more reasons it was rejected?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mcintyre321/ValueOf/issues/19#issuecomment-913454088, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACDJ6U3ED7MNHD23GSNHGDUAR3VNANCNFSM5BZYZR4Q . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
I'm also coming to the conclusion that codegen is probably the best way to fulfill these requirements.
Yes I have been thinking about that as well...
validation was compulsory - a delegate had to be provided in the constructor of each value object for validation
I actually really like this idea... any more reasons it was rejected?
It was rejected because of the extra heap allocation. This is partly understandable: if you previously had a struct
and now it's a class, that's one heap allocation, and if you then have a delegate passed to it, that's another, and if they're on particularly hot paths, then it may cause issues. My only answer to this was that Value Objects on their own aren't much use and that they're normally contained in a long-lived type, e.g. Price
would be part of Product
; Age
would be part of Person
etc., and those long-lived object would likely be on the heap anyway, so the original struct would also likely be on the heap.
In my (as yet imaginary) generated code version, it'll allow users to use a struct
or class
(but not record
)