Created
June 10, 2023 21:48
-
-
Save victoradan/448f5de1661472c577ddf948fa548123 to your computer and use it in GitHub Desktop.
This file contains 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 | |
/// First sketch. Simple domain types with shared generic Guid Id type. | |
module version1 = | |
type Role = | |
| Administrator | |
| Editor | |
| Viewer | |
type User = { Id: Guid; Name: string; Role: Role } | |
type State = | |
| ToDo | |
| InProgress | |
| Done | |
type Task = | |
{ Id: Guid | |
Title: string | |
State: State | |
Assignee: Option<Guid> } | |
/// Generic shared Guid type replaced by segregated, domain-informed Id types. | |
/// The functions are not leveraging the type system to encode authorization, | |
/// and are doing the authorization check themselves (at run time), | |
/// in addition to their main concern. | |
module version2 = | |
module User = | |
type UserId = UserId of Guid | |
type Role = | |
| Administrator | |
| Editor | |
| Viewer | |
type User = | |
{ Id: UserId; Name: string; Role: Role } | |
module Task = | |
open User | |
type TaskId = TaskId of Guid | |
type State = | |
| ToDo | |
| InProgress | |
| Done | |
type Task = | |
{ Id: TaskId | |
Title: string | |
State: State | |
Assignee: Option<UserId> } | |
let createTask (user: User) (title: string) : Option<Task> = | |
match user.Role with | |
| Administrator -> | |
Some | |
{ Id = TaskId(Guid.NewGuid()) | |
Title = title | |
State = ToDo | |
Assignee = None } | |
| Editor -> None | |
| Viewer -> None | |
let changeState (user: User) (state: State) (task: Task) : Option<Task> = | |
match user.Role with | |
| Administrator | |
| Editor -> Some { task with State = state } | |
| Viewer -> None | |
/// We leverage the type system to encode and enforce domain logic at the type level. | |
/// The once runtime checks are now compile-type checks. | |
module version3 = | |
module User = | |
type UserId = UserId of Guid | |
type User = { Id: UserId; Name: string } | |
type Administrator = Administrator of UserId | |
type Editor = Editor of UserId | |
type Viewer = Viewer of UserId | |
module Task = | |
open User | |
type TaskId = TaskId of Guid | |
type State = | |
| ToDo | |
| InProgress | |
| Done | |
type Task = | |
{ Id: TaskId | |
Title: string | |
State: State | |
Assignee: Option<UserId> } | |
let createTask (_: Administrator) (title: string) : Task = | |
{ Id = TaskId(Guid.NewGuid()) | |
Title = title | |
State = ToDo | |
Assignee = None } | |
/// We introduce an Auth module, to encode the autnorization rules and create `Auth` type values. | |
/// The domain functions now expect `Auth` types to prevent unauthorized calls at compile time. | |
module version4 = | |
module User = | |
type Role = | |
| Administrator | |
| Editor | |
| Viewer | |
type UserId = UserId of Guid | |
type User = | |
{ Id: UserId; Name: string; Role: Role } | |
module Auth = | |
open User | |
type Auth<'p> = | |
private | |
{ permission: 'p } // private constructor... | |
member this.Permission = this.permission // but public access attribute. | |
type CreateTask = CreateTask of UserId | |
type EditTask = EditTask of UserId | |
let getCreateTaskAuth (user: User) : Option<Auth<CreateTask>> = | |
match user.Role with | |
| Administrator -> Some { permission = CreateTask user.Id } | |
| Editor | |
| Viewer -> None | |
let getEditTaskAuth (user: User) : Option<Auth<EditTask>> = | |
match user.Role with | |
| Administrator | |
| Editor -> Some { permission = EditTask user.Id } | |
| Viewer -> None | |
module Task = | |
open Auth | |
type TaskId = TaskId of Guid | |
type State = | |
| ToDo | |
| InProgress | |
| Done | |
type Task = | |
{ Id: TaskId | |
Title: string | |
State: State | |
Assignee: Option<User.UserId> } | |
let createTask (_: Auth<CreateTask>) (title: string) : Task = | |
{ Id = TaskId(Guid.NewGuid()) | |
Title = title | |
State = ToDo | |
Assignee = None } | |
let changeState (_: Auth<EditTask>) (state: State) (task: Task) : Task = { task with State = state } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment