I've just finished reading Scott Wlaschin's excellent book "Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#" in which he uses the F# SQL type provider for the database access.

Anticipating the obvious question, he says (p260) not to use an ORM because you can't ensure the integrity of the domain that way. For example, an ORM can't validate an email address, etc, can't deal with nested choice types, etc.

However, earlier on in the book (can't find it offhand, so I hope I quote him correctly), when explaining how to create boundaries for each section of the workflow, he says that data should be validated at each section boundary, allowing the code within the section to assume it's all valid.

If so, why not treat the entities coming out of the ORM and into your code as crossing a boundary, and validate them there? That way, you get the benefits of the ORM, but ensure the integrity of your model.


To be clear, I said "In F#, we tend not to use an object-relational mapper (ORM)" but not because of any validation issues, rather because a big ORM like Entity Framework is heavily object-oriented (surprise!) which doesn't always mesh well with a functional paradigm. Instead F#ers will either use type providers or a simpler library such as Dapper.

In the "Working with Relational Databases" chapter, I do say that you can treat the database in two different ways:

  • as a untrusted source, in which case you do validation in the usual way (just as you would for data coming from a JSON Web API, say)
  • as a trusted source, in which case you don't do validation and throw exceptions if the data is bad.

Which case applies depends on whether the database is shared with other applications, how it is designed, etc.

Update: I realize that the following sentence could be misinterpreted:

Can’t we just use something like Entity Framework or NHibernate that will do all this mapping automatically? The answer is no, not if you want to ensure the integrity of your domain.

What I meant by that is that translating a domain model directly to a database via an ORM means that you lose a lot of the complexity of the domain types -- the "make illegal states unrepresentable" design approach that I dedicate a chapter to. This is because ORMs don't know how to handle single-case unions with constraints, complex choice types, etc.

Instead, I recommend that you translate your domain objects to a type specially designed for persistence (a "DTO") and then use that DTO for persistence, not the domain type. To store/load the DTO you can certainly use EF or NHibernate, but you're not getting much value from it when you use it that way, and so it's generally easier to use type providers or a lightweight mapper like Dapper.


If you read the next paragraph in the book, it says this:

What's special about using a type provider rather than a typical runtime library is that the SQL type provider will create types that match the SQL queries or SQL commands at compile time. If the SQL query is incorrect, you will get a compile time error, not a runtime error. And if the SQL is correct, it will generate an F# record type that matches the output of the SQL code exactly.

So it seems that this approach provides some of the same guarantees that static typing does in C#. Namely, that you will get an error at compile time if your types don't line up.

  • Hmm, good point, I'd hadn't thought of it that way. However, the ORM creates static types (the entities) anyway, and if I use those as input to be validated at the boundary of the section, then won't I get those same benefits with an ORM? May 22 '18 at 15:47
  • Yes, at runtime, with additional effort required. My understanding is that the F# type system is more sophisticated that that of C#. C# types must be supplemented with things like validation and code contracts to get the same benefits that F# already provides out of the box. May 22 '18 at 15:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.