Skip to content

Instantly share code, notes, and snippets.

@hodzanassredin
Last active June 13, 2016 15:02
Show Gist options
  • Save hodzanassredin/f708d9f9456dbbab36a7a64aea248df7 to your computer and use it in GitHub Desktop.
Save hodzanassredin/f708d9f9456dbbab36a7a64aea248df7 to your computer and use it in GitHub Desktop.
attempt to express erik evans ddd in fsharp with cqrs and other concepts like aggregate roots
open System
[<AutoOpen>]
module Common =
open System.Collections.Generic
type Result<'TSuccess,'TFailure> =
| Success of 'TSuccess
| Failure of 'TFailure
let isInvalidString min max str =
String.IsNullOrWhiteSpace(str) || str.Length > max || str.Length < min
type Index<'k,'v,'ev when 'k: equality>(keyName : string, entityName : string, ctor : 'v -> Result<'ev,_>, ctorBack : 'ev -> 'v) =
let idx = new Dictionary<'k, 'v>()
member x.Upsert k v =
if idx.ContainsKey k
then idx.[k] <- ctorBack v
else idx.Add(k, ctorBack v)
member x.GetByKey k fail =
if idx.ContainsKey k
then ctor idx.[k]
else Failure (sprintf "%s with %s key = %A doesnt exists" entityName keyName k)
member x.GetByKeyIn keys =
keys |> Seq.where idx.ContainsKey |> Seq.map (fun k -> ctor <| idx.[k])
module User =
open System.Security.Cryptography
open System.Text
let passwordSalt () =
let rng = new RNGCryptoServiceProvider()
let buff = Array.zeroCreate<byte> 32;
rng.GetBytes(buff)
Convert.ToBase64String(buff)
let getPasswordHash (pass:string) (salt:string) =
let bytes = Encoding.Unicode.GetBytes(pass)
let src = Encoding.Unicode.GetBytes(salt)
let dst = Array.zeroCreate<byte> (src.Length + bytes.Length)
Buffer.BlockCopy(src, 0, dst, 0, src.Length)
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length)
let algorithm = HashAlgorithm.Create("SHA1")
let inarray = algorithm.ComputeHash(dst)
Convert.ToBase64String(inarray)
//value
type Address = { street : string; city : string}
type PasswordState = { hash : string; salt : string}
type Password =
private { passwordState : PasswordState }
member x.State = x.passwordState
member x.CheckPassword password =
x.passwordState.hash = getPasswordHash password x.passwordState.salt
static member FromState(state : PasswordState) =
if String.IsNullOrWhiteSpace state.hash || String.IsNullOrWhiteSpace state.salt
then Failure "incorrect password data"
else Success({passwordState = state})
static member Create(password:string) =
if isInvalidString 5 50 password
then Failure "Not valid password"
else let salt = passwordSalt()
Password.FromState {salt = salt; hash = getPasswordHash password salt}
type UserState = {
id : Guid
email : string
name : string
password : Password
address : Address
isAdmin : bool }
//Entity is not anemic
type User =
private { userState : UserState }
member x.State = x.userState
static member fromState userState =
if userState.email.Contains("@") |> not
then Failure "Not valid Email"
else Success { userState = userState }
let private userIndex<'a when 'a : equality> keyName = Index<'a, UserState, User>("user", keyName, User.fromState, fun ({userState = state}) -> state)
type UserRepository() =
let idIdx = userIndex<Guid>("id")
let emailIdx = userIndex<string>("email")
let userUpdated = new Event<_>()
member x.GetById id = idIdx.GetByKey id
member x.GetByEmail email = emailIdx.GetByKey email
member x.Upsert(user : User) =
idIdx.Upsert user.userState.id user
emailIdx.Upsert user.userState.email user
userUpdated.Trigger(user)
Success()
member x.UserUpdate = userUpdated.Publish
module Channel =
type ChannelState = {
id : Guid
name : string
description : string
sourceUrl : Uri
showAsAdv : bool
userId : Guid
userName : string}
type Channel =
private { channelState : ChannelState}
member x.State = x.channelState
static member fromState state =
if isInvalidString 0 50 state.name then Failure "Not valid name"
else if isInvalidString 0 255 state.description then Failure "Not valid description"
else Success { channelState = state }
module ShortUser =
type ViewModel = {
id : Guid
name : string
}
type User.User with
member x.ToShortUser(user : User.User) =
{
id = user.State.id
name = user.State.name
}
type Channel.Channel with
member x.ToShortUser(channel : Channel.Channel) =
{
id = channel.State.userId
name = channel.State.userName
}
open User
let p = Password.Create("password")
match p with
| Success p -> let u = User.fromState({
id = Guid.NewGuid()
name = "hodza"
email = "[email protected]"
password = p
address = {street = "moskovskaya";
city = "spb"}
isAdmin = false})
match u with
| Success(u) -> printfn "check incorrect password %A" <| u.State.password.CheckPassword("incorrect")
printfn "check correct password %A" <| u.State.password.CheckPassword("password")
printfn "user %A" u.State
| Failure (f) -> printfn "fail %A" f
| Failure f-> printfn "fail %A" f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment