All Projects → lfr → Fsharp.domain.validation

lfr / Fsharp.domain.validation

Licence: mit
Designing with types requires a lot of code - this library fixes that

Programming Languages

fsharp
127 projects

Projects that are alternatives of or similar to Fsharp.domain.validation

Event Store Symfony Bundle
Event Store Symfony Bundle
Stars: ✭ 93 (-21.85%)
Mutual labels:  ddd
Dev Stuff
😎 Programming stuff for everyone. Collection of articles, videos about architecture, Domain Driven Design, microservices, testing etc.
Stars: ✭ 105 (-11.76%)
Mutual labels:  ddd
Java Interview
At the beginning, it was the repository with questions from Java interviews. Currently, it's more like knowledge base with useful links.
Stars: ✭ 114 (-4.2%)
Mutual labels:  ddd
Perfectwindows
PerfectWindows 软件家族 - Windows 从未如此完美!
Stars: ✭ 1,326 (+1014.29%)
Mutual labels:  tiny
Quiz
Example real time quiz application with .NET Core, React, DDD, Event Sourcing, Docker and built-in infrastructure for CI/CD with k8s, jenkins and helm
Stars: ✭ 100 (-15.97%)
Mutual labels:  ddd
Dddplus Demo
♨️ Demonstrate how to use DDDplus to build a complex OMS.演示如何使用DDDplus实现一套订单履约中台OMS
Stars: ✭ 111 (-6.72%)
Mutual labels:  ddd
Ddd Tdd Rich Domain Model Dojo Kata
DDD patterns implemented following TDD
Stars: ✭ 91 (-23.53%)
Mutual labels:  ddd
React Plain Router
🛣 A 2kb React router that works exactly as expected with native Javascript
Stars: ✭ 119 (+0%)
Mutual labels:  tiny
Clean Architecture
A (work-in-progress) guide to the methodology behind Made Tech Flavoured Clean Architecture
Stars: ✭ 101 (-15.13%)
Mutual labels:  ddd
Charm
A really tiny crypto library.
Stars: ✭ 116 (-2.52%)
Mutual labels:  tiny
Pdo Event Store
PDO implementation of ProophEventStore http://getprooph.org
Stars: ✭ 96 (-19.33%)
Mutual labels:  ddd
Sample Boot Hibernate
Spring Boot + JPA ( Hibernate ) + Java8 [ DDD Sample ]
Stars: ✭ 97 (-18.49%)
Mutual labels:  ddd
Bifrost
This is the stable release of Dolittle till its out of alpha->beta stages
Stars: ✭ 111 (-6.72%)
Mutual labels:  ddd
Unissist
⛑ A ~300b unistore helper to persist your data using equally tiny storage adapters
Stars: ✭ 94 (-21.01%)
Mutual labels:  tiny
Ipfs Mini
A super tiny module for querying IPFS that works in the browser and node.
Stars: ✭ 115 (-3.36%)
Mutual labels:  tiny
Event Store Client
PHP 7.4 Event Store Client Implementation
Stars: ✭ 93 (-21.85%)
Mutual labels:  ddd
Dddplus
🔥 A lightweight flexible development framework for complex business architecture with full ecosystem!轻量级业务中台开发框架,中台架构的顶层设计和完整解决方案!
Stars: ✭ 107 (-10.08%)
Mutual labels:  ddd
Tiny Renderer
A tiny sotfware 3D renderer in 100 lines of Python
Stars: ✭ 120 (+0.84%)
Mutual labels:  tiny
Java Ddd Example
☕🎯 Hexagonal Architecture + DDD + CQRS in a Java project using SpringBoot
Stars: ✭ 119 (+0%)
Mutual labels:  ddd
Ddd On Scala
DDD sample implementation by Scala.
Stars: ✭ 113 (-5.04%)
Mutual labels:  ddd

nuget nuget

A tiny F# library with huge potential to simplify your domain design, as you can see from the examples below: | Without this package 👎 | Using this package 👍 | |---|---| |

// Single-case union style
type Tweet = private Tweet of string
module Tweet =
let validate = function
| s when String.IsNullOrWhitespace s →
IsMissingOrBlank |> Error
| s when s.Length > 280 →
IsTooLong 280 |> Error
| s → Tweet s |> Ok
let value (Tweet s) = x in s

// Object-oriented style
type Tweet private (s) = class end with
static member Validate = function
| s when String.IsNullOrWhitespace s →
IsMissingOrBlank |> Error
| s when s.Length > 280 →
IsTooLong 280 |> Error
| s → Tweet s |> Ok
interface IConstrained<string> with
member x.Value = s
|
type Tweet = private Tweet of Text with
interface TextBox with
member _.Validate =
fun s -> s.Length > 280 => IsTooLong 280
         ➡ See the live demo

You may have noticed that the examples on the left have an additional not null or empty validation case. On the right this validation is implicit in the statement that a Tweet is a Tweet of Text. Since Validation boxes can have inner boxes, the only rules that need to be explicitly declared are the rules specific to the type being defined!

Interface? Really?

F# is a multi-paradigm language, so there's nothing preventing us from harnessing (hijacking?) OP concepts for their expressiveness without any of the baggage. For instance here we use interface as an elegant way to both:

  • Identify a type as a Validation box
  • Enforce the definition of validation rules

There's no other mentions of interfaces in the code that creates or uses Validation boxes, only when defining new types.

How it works

First you declare your error types, then you declare your actual domain types (i.e. Tweet), and finally you use them with the provided Box.value and Box.validate functions. These 3 simple steps are enough to ensure at compilation time that your entire domain is always valid!

demo
Older version of the live demo for future DDD paleontologists

Declaring your errors

Before declaring types like the one above, you do need define your error type. This can be a brand new validation-specific discriminated union or part of an existing one.

// These are just an example, create whatever errors
// you need to return from your own validation rules
type TextError =
    | ContainsControlCharacters
    | ContainsTabs
    | IsTooLong of int
    | IsMissingOrBlank
    // ...

While not strictly necessary, the next single line of code greatly improves the readability of your type declarations by abbreviating the IBox<_,_> interface for a specific primitive type.

// all string-based types can now interface TextBox instead of IBox<string, TextError>
type TextBox = inherit IBox<string, TextError>

Declaring your types

Type declaration is reduced to the absolute minimum. A type is given a name, a private constructor, and the interface above that essentially makes it a Validation box and ensures that you define the validation rule.

The validation rule is a function of the primitive type (string here) that returns a list of one or more errors depending on the stated conditions.

/// Single or multi-line non-null non-blank text without any additional validation
type FreeText = private FreeText of string with
    interface TextBox with
        member _.Validate =
            // validation ruleᵴ (only one)
            fun s ->
                [if s |> String.IsNullOrWhiteSpace then IsMissingOrBlank]

Simpler validation rules with validation operators

The type declaration above can be simplified further using the provided => and ==> operators that here combine a predicate of string with the appropriate error.

/// Alternative type declaration using the ==> operator
type FreeText = private FreeText of string with
    interface TextBox with
        member _.Validate =
            // same validation rule using validation operators
            String.IsNullOrWhiteSpace ==> IsMissingOrBlank

To use validation operators make sure to open FSharp.Domain.Validation.Operators in the file(s) where you declare your Validation types. See Text.fs for more examples of validation operators.

Creating and using boxes in your code

Using Validation boxes is easy, let's say you have a box called email, you can simply access its value using the following:

// get the primitive value from the box
Box.value email // → string

There's also an experimental operator % that essentially does the same thing. Note that this operator is opened automatically along with the namespace FSharp.Domain.Validation. To avoid operator pollution this is advertised as experimental until the final operator characters are decided.

// experimental — same as Box.value
%email // → string

Creating a box is just as simple:

// create a box, canonicalizing (i.e. trimming) the input if it's a string
Box.validate s // → Ok 'box | Error e

Box.validate canonicalization consists of trimming both whitespace and control characters, as well as removing occurrences of the null character. While this should be the preferred way of creating boxes, it's possible to skip canonicalization by using Box.verbatim instead.

When type inference isn't possible, specify the box type using the generic parameter:

// create a box when its type can't be inferred
Box.validate<Tweet> s // → Ok Tweet | Error e

⚠ Do not force type inference using type annotations as it's unnecessarily verbose:

// incorrect example, do *not* copy/paste
let result : Result<Email, TextError list> = // :(
    Box.validate "[email protected]"

// correct alternative when type inference isn't available
let result =
    Box.validate<Email> "[email protected]"    // :)

In both cases result is of type Result<Email, TextError list>.

Exceptions instead of Error

The Box.validate method returns a Result, which may not always be necessary, for instance when de-serializing values that are guaranteed to be valid, you can just use:

// throws an exception if not valid
Unchecked.boxof "this better be valid"         // → 'box (inferred)

// same as above, when type inference is not available
Unchecked.boxof<Text> "this better be valid 2" // → Text

Serialization

There's a System.Text.Json.Serialization.JsonConverter included, if you add it to your serialization options all boxes are serialized to (and de-serialized from) their primitive type. It is good practice to keep your serialized content independent from implementation considerations such as Validation boxes.

Not just strings

Strings are the perfect example as it's usually the first type for which developers stitch together validation logic, but this library works with anything, you can create a PositiveInt that's guaranteed to be greater than zero, or a FutureDate that's guaranteed to not be in the past. Lists, vectors, any type of object really, if you can write a predicate against it, you can validate it. It's 100% generic so the sky is the limit.

Ok looks good, but I'm still not sure

I've created a checklist to help you decide whether this library is a good match for your project:

  • [x] My project contains domain objects/records

If your project satisfies all of the above this library is for you!

It dramatically reduces the amount of code necessary to make illegal states unrepresentable while being tiny and built only with FSharp.Core. It uses F# concepts in the way they're meant to be used, so if one day you decide to no longer use it, you can simply get rid of it and still keep all the single-case unions that you've defined. All you'll need to do is create your own implementation of Box.validate and Box.value or just make the single case constructors public.

Ready to try it?

There are two packages, make sure you only reference the one you need:

Project type Package
Standard nuget
Fable nuget

You can check the project source code behind the live demo. You can also look into Text.fs for an example of string boxes which are the by far the most common type of boxes.

Conclusion

Using this library you can create airtight domain objects guaranteed to never have invalid content. Not only you're writing less code, but your domain definition files are much smaller and nicer to work with. You'll also get ROP almost for free, and while there is a case to be made against ROP, it's definitely a perfect match for content validation, especially content that may be entered by a user.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].