Skip to content

Instantly share code, notes, and snippets.

@victoradan
Created June 10, 2023 21:48
Show Gist options
  • Save victoradan/448f5de1661472c577ddf948fa548123 to your computer and use it in GitHub Desktop.
Save victoradan/448f5de1661472c577ddf948fa548123 to your computer and use it in GitHub Desktop.
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