Skip to content

Commit

Permalink
Use a Todo list as an example
Browse files Browse the repository at this point in the history
  • Loading branch information
samhatoum committed May 27, 2020
1 parent fdf1a3d commit c524c48
Show file tree
Hide file tree
Showing 26 changed files with 344 additions and 161 deletions.
28 changes: 0 additions & 28 deletions src/domain/file/File.spec.ts

This file was deleted.

27 changes: 0 additions & 27 deletions src/domain/file/File.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/domain/file/FileCommandHandlers.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/domain/file/FileCommands.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/domain/file/FileEvents.ts

This file was deleted.

95 changes: 95 additions & 0 deletions src/domain/todo-list/TodoList.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {GivenWhenThen} from '../../framework/GivenWhenThen'
import {guid} from '../../framework/Guid'
import {
AddTodoToList,
CreateTodoList,
MarkTodoAsComplete,
RenameTodoList,
} from './TodoListCommands'
import {
TodoAddedToList,
TodoListCreated,
TodoListRenamed,
TodoMarkedAsComplete,
} from './TodoListEvents'
import {
DuplicateEntryError,
MissingParameterError,
NotFoundError,
} from '../../framework/Errors'

const GWT = GivenWhenThen(`${__dirname}/TodoList`)

test('Create todoList', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

When(new CreateTodoList(todoListId, 'bar'))
Then(new TodoListCreated(todoListId, 'bar'))
}))

test('Rename todoList', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(new TodoListCreated(todoListId, 'foo'))
When(new RenameTodoList(todoListId, 'bar', 1))
Then(new TodoListRenamed(todoListId, 'bar'))
}))

test('Rename todoList fail', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(new TodoListCreated(todoListId, 'foo'))
When(new RenameTodoList(todoListId, '', 1))
expect((): void => {
Then(new TodoListRenamed(todoListId, 'bar'))
}).toThrow(MissingParameterError)
}))

test('Add todo to list', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(new TodoListCreated(todoListId, 'foo'))
When(new AddTodoToList(todoListId, 'feed the cat', 1))
Then(new TodoAddedToList(todoListId, 'feed the cat'))
}))

test('Add todo to list fails for duplicate name todos', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(
new TodoListCreated(todoListId, 'foo'),
new TodoAddedToList(todoListId, 'feed the cat')
)
When(new AddTodoToList(todoListId, 'feed the cat', 1))
expect((): void => {
Then(new TodoAddedToList(todoListId, 'feed the cat'))
}).toThrow(DuplicateEntryError)
}))

test('Mark todo as complete', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(
new TodoListCreated(todoListId, 'foo'),
new TodoAddedToList(todoListId, 'feed Bob')
)
When(new MarkTodoAsComplete(todoListId, 'feed Bob', 1))
Then(new TodoMarkedAsComplete(todoListId, 'feed Bob'))
}))

test('Mark todo as complete fails', () =>
GWT((Given, When, Then) => {
const todoListId = guid()

Given(new TodoListCreated(todoListId, 'foo'))
When(new MarkTodoAsComplete(todoListId, 'feed Bob', 1))
expect((): void => {
Then(new TodoMarkedAsComplete(todoListId, 'feed Bob'))
}).toThrow(NotFoundError)
}))
63 changes: 63 additions & 0 deletions src/domain/todo-list/TodoList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {AggregateRoot} from '../../framework/AggregateRoot'
import {
TodoAddedToList,
TodoListCreated,
TodoListRenamed,
TodoMarkedAsComplete,
} from './TodoListEvents'
import {
DuplicateEntryError,
MissingParameterError,
NotFoundError,
} from '../../framework/Errors'

class Todo {
complete: boolean
constructor(public readonly name: string) {}
}

export class TodoList extends AggregateRoot {
private name: string
private todos: Todo[] = []

constructor(guid: string, name: string) {
super()
this.applyChange(new TodoListCreated(guid, name))
}
applyTodoListCreated(event: TodoListCreated): void {
this._id = event.aggregateId
this.name = event.name
}

rename(name: string): void {
if (!name) throw new MissingParameterError('Must provide a name')
this.applyChange(new TodoListRenamed(this._id, name))
}
applyTodoListRenamed(event: TodoListRenamed): void {
this._id = event.aggregateId
this.name = event.name
}

addTodo(todoName: string): void {
if (this.todos.find((todo) => todo.name === todoName)) {
throw new DuplicateEntryError(`${todoName} already exists`)
}
this.applyChange(new TodoAddedToList(this._id, todoName))
}
applyTodoAddedToList(event: TodoAddedToList): void {
this.todos.push(new Todo(event.todoName))
}

markTodoAsComplete(todoName: string): void {
if (!this.todos.find((todo) => todo.name === todoName)) {
throw new NotFoundError(`${todoName} not found`)
}
this.applyChange(new TodoMarkedAsComplete(this._id, todoName))
}
applyTodoMarkedAsComplete(event: TodoMarkedAsComplete): void {
const matchedTodo = this.todos.find(
(todo: Todo) => todo.name === event.todoName
)
matchedTodo.complete = true
}
}
35 changes: 35 additions & 0 deletions src/domain/todo-list/TodoListCommandHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {IRepository} from '../../framework/Repository'
import {TodoList} from './TodoList'
import {
AddTodoToList,
CreateTodoList,
MarkTodoAsComplete,
RenameTodoList,
} from './TodoListCommands'

export class TodoListCommandHandlers {
constructor(private _repository: IRepository<TodoList>) {}

handleCreateTodoList(command: CreateTodoList): void {
const todoList = new TodoList(command.aggregateId, command.name)
this._repository.save(todoList, -1)
}

handleRenameTodoList(command: RenameTodoList): void {
const todoList = this._repository.getById(command.aggregateId)
todoList.rename(command.name)
this._repository.save(todoList, command.expectedAggregateVersion)
}

handleAddTodoToList(command: AddTodoToList): void {
const todoList = this._repository.getById(command.aggregateId)
todoList.addTodo(command.todoName)
this._repository.save(todoList, command.expectedAggregateVersion)
}

handleMarkTodoAsComplete(command: MarkTodoAsComplete): void {
const todoList = this._repository.getById(command.aggregateId)
todoList.markTodoAsComplete(command.todoName)
this._repository.save(todoList, command.expectedAggregateVersion)
}
}
40 changes: 40 additions & 0 deletions src/domain/todo-list/TodoListCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {Command} from '../../framework/Command'

export class CreateTodoList extends Command {
constructor(
public readonly aggregateId: string,
public readonly name: string
) {
super(-1)
}
}

export class AddTodoToList extends Command {
constructor(
public readonly aggregateId: string,
public readonly todoName: string,
public readonly expectedAggregateVersion: number
) {
super(expectedAggregateVersion)
}
}

export class MarkTodoAsComplete extends Command {
constructor(
public readonly aggregateId: string,
public readonly todoName: string,
public readonly expectedAggregateVersion: number
) {
super(expectedAggregateVersion)
}
}

export class RenameTodoList extends Command {
constructor(
public readonly aggregateId: string,
public readonly name: string,
public readonly expectedAggregateVersion: number
) {
super(expectedAggregateVersion)
}
}
37 changes: 37 additions & 0 deletions src/domain/todo-list/TodoListEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {Event} from '../../framework/Event'

export class TodoListCreated extends Event {
constructor(
public readonly aggregateId: string,
public readonly name: string
) {
super(aggregateId)
}
}

export class TodoAddedToList extends Event {
constructor(
public readonly aggregateId: string,
public readonly todoName: string
) {
super(aggregateId)
}
}

export class TodoMarkedAsComplete extends Event {
constructor(
public readonly aggregateId: string,
public readonly todoName: string
) {
super(aggregateId)
}
}

export class TodoListRenamed extends Event {
constructor(
public readonly aggregateId: string,
public readonly name: string
) {
super(aggregateId)
}
}
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit c524c48

Please sign in to comment.