Last active
June 13, 2016 15:02
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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