forked from xolvio/typescript-event-sourcing
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
344 additions
and
161 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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
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) | ||
})) |
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
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 | ||
} | ||
} |
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
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) | ||
} | ||
} |
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
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) | ||
} | ||
} |
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
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.
Oops, something went wrong.