building javascript applications based on ddd, cqrs and eventsourcing
DESCRIPTION
Video: http://www.youtube.com/watch?v=XSc7NPedAxw&feature=youtu.be Model-View-Controller (MVC) was born in the 1970s and is still one of the most used architectural patterns in the software world. On top of that it's common to connect Data-Models using Object-relational Mapping (ORM) to a Database and provide easy Create-Read-Update-Delete (CRUD) Routes as an API for applications. In this talk I will show an alternative approach and how to easily apply it using JavaScript. Live coding included. In the process the audience will learn about Domain-driven Design (DDD), Command Query Responsibility Seggregation (CQRS), EventSourcing and how to explicitly capture your domain events - something the ThoughtWorks TechnologyRadar2014 also outlined as an emerging technique, because it makes developers and business people happy.TRANSCRIPT
Building JavaScript applicationsbased on DDD, CQRS and EventSourcing
MunichJS, 04. Sept 2014
DomainDomain Model Model
ORMORM
CRUDCRUD
Updates Manipulates
Sees Uses
Our Domain
We are a SchoolSchoolWe have StudentsStudents and CoursesCourses
Students should be able to- join a Course- leave a Course
var studentSchema = new MongooseSchema({ name: String});
var courseSchema = new MongooseSchema({ name: String});
Relationships with ORM
Students should be able to join Courses
var courseSchema = new MongooseSchema({ name: String, students: [ studentSchema ]});
app = express()
// C(reate)app.post('/course/create', course.create);
// R(ead)app.get('/course', course.read);app.get('/course/:id', course.read);
// U(pdate)app.post('/course/update/:id', course.update);
// D(elete)app.get('/course/delete/:id', course.delete);
Where did theWhere did theBusiness-Logic go?Business-Logic go?
Business -
Reporting
Which Students first joined a Course but left it again?
var courseSchema = new MongooseSchema({ name: String, students: [ studentSchema ], studentsLeftIds: Array});
// U(pdate) Routecourse.update = function(courseId, updatedCourse) { currentCourse = course.findById(courseId);
// U(pdate) Routecourse.update = function(courseId, updatedCourse) { currentCourse = course.findById(courseId);
// add students to "studentsLeft" that left the course currentCourse.studentsLeftIds = calculateStudentsLeftTheCourse(currentCourse,updatedCourse);
// save currentCourse.save()};
// U(pdate) Routecourse.update = function(courseId, updatedCourse) { currentCourse = course.findById(courseId);
// remove students from "studentsLeft" that enrolled again currentCourse.studentsLeftIds = calculateStudentsJoinedTheCourseAgain(currentCourse, upatedCourse);
// add students to "studentsLeft" that left the course currentCourse.studentsLeftIds = calculateStudentsLeftTheCourse(currentCourse, updatedCourse);
// save currentCourse.save()};
Another approachAnother approach
BoundedContext
Encapsulates your DomainModel(s)
var school = eventric.context('school');
Ubiquitous Language
Behaviors in your Domain
EventStormingEventStorming
DomainEventsDomainEvents
school.defineDomainEvents({ StudentJoinedCourse: function(params) { this.courseId = params.courseId; }, StudentLeftCourse: function(params) { this.courseId = params.courseId; }});
Aggregate
school.addAggregate('Student', function() { this.joinInCourse = function(courseId) { this.$emitDomainEvent('StudentJoinedCourse', { courseId: params.courseId }); }
school.addAggregate('Student', function() { this.joinInCourse = function(courseId) { this.$emitDomainEvent('StudentJoinedCourse', { courseId: params.courseId }); } this.leaveCourse = function(courseId) { this.$emitDomainEvent('StudentLeftCourse', { courseId: params.courseId }); }});
CommandHandler
school.addCommandHandlers({
StudentJoinCourse: function(params, done) { studentRepository = this.$repository('Student'); studentRepository.findById(params.studentId) .then(function(student) {
student.joinCourse(params.courseId);student.joinCourse(params.courseId);
studentRepository.save(params.studentId); }) .then(function() { done(); }) }
StudentLeaveCourse: function(params, done) { studentRepository = this.$repository('Student'); studentRepository.findById(params.studentId) .then(function(student) {
student.leaveCourse(params.courseId);student.leaveCourse(params.courseId);
studentRepository.save(params.studentId); }) .then(function() { done(); }) }
});
Reporting using Projections
school.addProjection(function() { this.studentsLeft = {}
this.handleStudentLeftCourse = function(domainEvent) { // make sure the object has the correct format this.studentsLeft[domainEvent.aggregate.id] .course[domainEvent.payload.courseId] = true; } });
school.addProjection(function() { this.studentsLeft = {}
this.handleStudentJoinedCourse = function(domainEvent) { // make sure the object has the correct format delete this.studentsLeft[domainEvent.aggregate.id] .course[domainEvent.payload.courseId] }
this.handleStudentLeftCourse = function(domainEvent) { // make sure the object has the correct format this.studentsLeft[domainEvent.aggregate.id] .course[domainEvent.payload.courseId] = true; } });
CQRS + EventSourcingCQRS + EventSourcing
to the Livecoding..to the Livecoding..
Johannes Becker@dieserjohannes
eventricjs.orgeventricjs.org
github.com/efacilitation/eventricgithub.com/efacilitation/eventricgithub.com/efacilitation/eventric-todoMVCgithub.com/efacilitation/eventric-todoMVC