A tH (4) [Avatar] Offline
#1
Following some examples in the book I have several Entities which have a 'Create' func (+ private ctor) which takes other entities/value objects and return an Entity or Option<Entity>. These are accompanied by a 'CreateValidEntity' method/func which takes primitive values and returns a Validation<Entity>.
This works wonderfully when creating data from a command which is send to the API from some client, but seems a bit much when re-instating entities from database values (also primitive). I was wondering if you have any thoughts on how to do this effectively?

Since the database records also have primitive values (and these, in theory, could get corrupted) my intuition is to use the 'CreateValidEntity' version. But the amount of code needed seems a bit much. Would you also use a 'CreateValidEntity' method for this? And then make sure this could be used when creating an entity from a client of the Api or when re-instating entities from database?

Another issue with this is that I end up with a 'Validation' and would like an 'Option'. Sure I can match the Validation and turn it into an Option, but the fact that data is corrupt would then get lost, or needs to be logged somewhere. Would you throw an Exception in case of corrupt data or handle it in the "Errors are business as usual" manner?

I'm using Dapper for SQL access, btw.
Enrico Buonanno (96) [Avatar] Offline
#2
Well, I'd say, use `Validation<Entity>` if there is a chance that the data is invalid, and you want to model that.

So, if you do validation before persisting the data, and are therefore confident that the persisted data is valid, you can just use `Entity` when reinstating it from the DB. Keep in mind that "entity" is already a rather OO concept, in FP you just think about "data".

To go from `Validation` to `Option` you can define a method `ToOption : Validation<T> -> Option<T>`, which maps `Invalid` to `None` and `Valid<T>` to `Some<T>`; this means that in the `Invalid` case the error information is discarded; if you want to log the error, write an enhanced version of this method.

Hope this helps
A tH (4) [Avatar] Offline
#3
Thank you for your answer, I have a question concerning the re-instating of the Entity.

When I would assume the data is valid (since it was validated before being persisted) and would like to re-instate it, versus creating a Validation<Entity>, some Entities require another custom type in their Create method, let's pretend the method requires the 'Age' example from the book. This Age would be persisted as an integer in the database, and the only way to Create an Age is via its smart constructor (.Of(int age)), but this returns an Option<Age> (rightfully so) and our Create method for the Entity needs an Age, not an Option<Age>.

How would you get around this besides Matching the Option<Age> when re-instating the Entity? This significantly bloats the creation code. I could write a helper that chains '.AsEnumerable().Single()' (single from Linq) which assumes the Option is Some and forces it to return the Age, but I wonder whether this would be accepted in your opinion? I can't think of any other ways to create the Age besides using its smart constructor. How would you handle this?

Btw, I use the word Entity, but mean something with identity, I do not model behavior on these types (only checks they are valid, when not valid returning an Option).
Enrico Buonanno (96) [Avatar] Offline
#4
Thanks for your message. You raise some interesting practical concerns.

When I would assume the data is valid (since it was validated before being persisted) and would like to re-instate it, versus creating a Validation<Entity>, some Entities require another custom type in their Create method, let's pretend the method requires the 'Age' example from the book. This Age would be persisted as an integer in the database, and the only way to Create an Age is via its smart constructor (.Of(int age)), but this returns an Option<Age> (rightfully so) and our Create method for the Entity needs an Age, not an Option<Age>.


If you have nested types that use, say, `Validation`, then stick to `Validation` everywhere. This is demonstrated in the book, section 8.5.1 "Validation with smart constructors" and the following sections.

How would you get around this besides Matching the Option<Age> when re-instating the Entity? This significantly bloats the creation code.


I would try to keep your data inside a `Validation` for as long as possible; try to only use `Match` at the outer edge of your app, as explained in Chapter 6. This should keep your code terse and readable.

I could write a helper that chains '.AsEnumerable().Single()' (single from Linq) which assumes the Option is Some and forces it to return the Age, but I wonder whether this would be accepted in your opinion?


I would avoid that, because `Single` can throw an exception, so it's not functional error handling

I can't think of any other ways to create the Age besides using its smart constructor. How would you handle this?


So, using smart constructors is done in order to only create valid data. Say you have a `Customer` with a `Name` and an `Age`, and the following constructors:

Age.Create : int -> Validation<Age>
Name.Create : string -> string -> Validation<Name>
Customer.Create : Name -> Age -> Customer


then when you're creating or updating a single customer, you want to ensure the data is valid, so you may end up with something like:

from age in Age.Create(30)
from name in Name.Create("Joe", "Higgins")
select Customer.Create(name, age)


Now let's make things a bit messier, let's assume that `Age.Create` returns an `Option`, while `Customer.Create` also performs some validation, so returns a `Validation`:

Age.Create : int -> Option<Age>
Name.Create : string -> string -> Validation<Name>
Customer.Create : Name -> Age -> Validation<Customer>


Then you can use some helper methods...

ToValidation : Option<T> -> string -> Validation<T>
Flatten : Validation<Validation<T>> -> Validation<T>


and you may end up with something like:

Valid(Customer.Create)
   .Apply(Name.Create("Joe", "Higgins")
   .Apply(Age.Create(x).ToValidation($"'{x}' is not a valid age")
   .Flatten()


On the other hand, if you're reading potentially millions of Customers from the database, to calculate some statistical value, it would be both costly and unnecessary to use the same `Customer` type; so you may want to use a different representation for reading data. This would be very natural in a CQRS scenario, where the "read" and "write" sides are completely different apps. Of course, this does not mean you must necessarily have two versions of every type in every app; there's a tradeoff between performance and simplicity... really it depends on your needs; the point is, if you're reading (rather than creating or updating) and don't really need the validation, then there's no point using a smart-constructor type; you may want to consider having a dumb version for reading.
A tH (4) [Avatar] Offline
#5
Thank you for your answer.

I do indeed use query types (consisting of primitive properties) for the read side. The questions I raised were specifically addressing the write/command side of things, when I need to update an entity and am dealing with re-instating an entity from the database before updating it.

So, if I understand correctly, there is no shortcut when re-instating an entity from database data (which would could assume is valid) which are primitive types versus creating an entity from a command?

What would you do then if the data turns out to be Invalid? Now I have a delegate that returns an Option<T> that is None in case the entity is Invalid, but the user firing the update command then gets an error saying "Cannot find entity by given id" which does not truly reflect the situation. Would you deal with this in a similar manner?
A mechanism could be made that returns a Option<Validation<T>>, but this seems overkill and could mean that the user gets a message stating "Name is not valid" when the database name is Invalid and could be interpreted by the client that the name he/she sent was Invalid.

Hope my concerns make sense. For now I'm just returning a None when the entity is Invalid.

Thanks again, I appreciate your time and really enjoy the book.