{"id":13826206,"url":"https://github.com/efacilitation/eventric","last_synced_at":"2025-04-09T15:04:19.135Z","repository":{"id":16972298,"uuid":"19734907","full_name":"efacilitation/eventric","owner":"efacilitation","description":"Minimalist JavaScript framework to build applications based on DDD, CQRS and Event Sourcing. ","archived":false,"fork":false,"pushed_at":"2017-01-10T15:01:34.000Z","size":1270,"stargazers_count":177,"open_issues_count":6,"forks_count":10,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-09T15:01:55.325Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/efacilitation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-13T10:29:09.000Z","updated_at":"2025-01-30T16:49:45.000Z","dependencies_parsed_at":"2022-09-11T06:50:44.188Z","dependency_job_id":null,"html_url":"https://github.com/efacilitation/eventric","commit_stats":null,"previous_names":[],"tags_count":76,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efacilitation%2Feventric","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efacilitation%2Feventric/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efacilitation%2Feventric/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efacilitation%2Feventric/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/efacilitation","download_url":"https://codeload.github.com/efacilitation/eventric/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055276,"owners_count":21040156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T09:01:33.880Z","updated_at":"2025-04-09T15:04:19.095Z","avatar_url":"https://github.com/efacilitation.png","language":"CoffeeScript","funding_links":[],"categories":["CoffeeScript","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"![eventric logo](https://raw.githubusercontent.com/wiki/efacilitation/eventric/eventric_logo.png)\n\n# eventric.js [![Build Status](https://travis-ci.org/efacilitation/eventric.svg?branch=master)](https://travis-ci.org/efacilitation/eventric)\n\nMinimalist JavaScript framework to build applications based on DDD, CQRS and Event Sourcing.\nSupports (micro)service based architectures and focuses on high [testability](https://github.com/efacilitation/eventric-testing).\n\neventric is written in CoffeeScript. If you need a JavaScript tutorial please compile the snippets below yourself.\n\n### Current road map\n\nCurrently there is an event store implementation for MongoDB which **will not work correctly in a multi process scenario.**\nWe will soon be working on an event store implementation for\n[http://geteventstore.com](http://geteventstore.com) which will get rid of this limitation.\n\nImplementations for other databases are currently not planned.\n\n## Tutorial\n\nThis tutorial will guide you through all features of eventric by implementing a simplified Todo application.\n\nThere is no API documentation. If you want to dig deeper, have a look at the source code and the specs.\n\n### Installation\n\nInstall the framework inside your application with `npm install eventric`.\n\neventric requires [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).\nIf you have to support older browsers install and use a polyfill, such as\n[es6-promise](https://github.com/jakearchibald/es6-promise) or [rsvp.js](https://github.com/tildeio/rsvp.js).\n\n### Context\n\nFirst create a Todo context inside your application.\n\n```coffeescript\ntodoContext = eventric.context 'Todo'\n```\n\nContexts create architectural boundaries inside an eventric based application.\nThey can be compared to (micro)services and somewhat also to bounded contexts from an implementation perspective.\n\nA context holds its own event store and provides a self contained space for domain events, aggregates, command and query handlers, projections and an event publishing infrastructure (which may be outsourced soon).\n\n### Domain events\n\nAfter creating the context the necessary domain events can be defined.\n\n```coffeescript\ntodoContext.defineDomainEvents\n  TodoCreated: ({title}) -\u003e\n    @title = title\n\n  TodoTitleChanged: ({title}) -\u003e\n    @title = title\n\n  TodoFinished: -\u003e\n\n```\n\nDomain event definitions in eventric consist of two parts: The domain event name and the payload constructor function.\nThis definition is similar to a class used in statically typed languages such as Java or C#.\nInside the payload constructor function the expected values must be assigned to members of `this`.\n\n*Note:* It is best practice to use the same name for parameters and assigned member variables.\n\n\n### Aggregates\n\nDomain events in eventric can only be emitted from inside aggregate instances.\nAn aggregate is defined through a plain class which must at least implement a `create()` function.\nThis function will be automatically called when creating a new aggregate from inside a command handler.\n\nUse the injected function `$emitDomainEvent(eventName, eventPayload)` to emit a new domain event.\nThis will cause two things to happen:\n- The new domain event is saved in the list of new domain events\n- If existing, the correct handle function on the aggregate is executed (`handle\u003cevent name\u003e()`)\n\nThe above mentioned handle functions are also used when loading and rebuilding an aggregate to create the current state.\n\n```coffeescript\ntodoContext.addAggregate 'Todo', class Todo\n\n  create: ({title}) -\u003e\n    if not title\n      throw new Error 'title missing'\n    @$emitDomainEvent 'TodoCreated',\n      title: title\n\n\n  changeTitle: ({title}) -\u003e\n    if not title\n      throw new Error 'title missing'\n    if @isFinished\n      throw new Error 'todo already finished'\n\n    @$emitDomainEvent 'TodoTitleChanged',\n      title: title\n\n\n  finish: -\u003e\n    @$emitDomainEvent 'TodoFinished'\n\n\n  handleTodoFinished: -\u003e\n    @isFinished = true\n\n\n```\n\n*Note:* Aggregate functions (except for handle functions) can return promises. eventric will wait for them to resolve.\n\n\n## Command handlers\n\nCommand handlers define the write side of the application service layer inside a context.\n\n### Definition\n\nCommand handlers are registered by passing an object to the context where the keys define the command names.\n\n```coffeescript\ntodoContext.addCommandHandlers\n\n  CreateTodo: ({title}) -\u003e\n    @$aggregate.create 'Todo',\n      title: title\n    .then (todo) -\u003e\n      todo.$save()\n\n\n  ChangeTodoTitle: ({todoId, title}) -\u003e\n    @$aggregate.load 'Todo', todoId\n    .then (todo) -\u003e\n      todo.changeTitle title: title\n      todo.$save()\n\n\n  FinishTodo: ({todoId}) -\u003e\n    @$aggregate.load 'Todo', todoId\n    .then (todo) -\u003e\n      todo.finish()\n      todo.$save()\n\n\n```\n\nUse the injected service `$aggregate` to create, load, modify and save aggregate instances.\nCreating an aggregate will cause the `create()` function defined on the aggregate class to be called.\nExecute the injected function `$save()` to save new domain events and publish them via the event bus.\n\nAlthough discouraged, queries can be executed by using the injected service `$query`.\n\n### Execution\n\nAfter defining the command handlers they can be executed from outside the context.\nIn order to work with a context it needs to be initialized.\nThe initialization is mainly required for projections.\n\n```coffeescript\ntodoContext.initialize()\n.then -\u003e\n  todoContext.command 'CreateTodo', title: 'My first todo'\n.then (todoId) -\u003e\n  todoContext.command 'ChangeTodoTitle',\n    todoId: todoId\n    title: 'My first changed todo'\n  .then -\u003e\n    todoContext.command 'FinishTodo',\n      todoId: todoId\n.then -\u003e\n  console.log 'todo created, changed and finished'\n```\n\n\n## Domain event handler\n\nDomain event handlers can be registered for specific events and even for specific aggregate instances.\n\n```coffeescript\ntodoContext.subscribeToDomainEvent 'TodoFinished', (domainEvent) -\u003e\n  console.log 'finished todo', domainEvent.aggregate.id\n\ntodoContext.subscribeToDomainEventWithAggregateId 'TodoTitleChanged', 'some aggregate id', (domainEvent) -\u003e\n  console.log 'change title to: ', domainEvent.payload.title\n```\n\n## Projections\n\nProjections always replay an event stream from the beginning.\nThey are used to create or populate read models.\n\n```coffeescript\ntodosReadModel = {}\n\ntodosProjection =\n\n  initialize: (params, done) -\u003e\n    done()\n\n\n  handleTodoCreated: (domainEvent) -\u003e\n    todosReadModel[domainEvent.aggregate.id] =\n      title: domainEvent.payload.title\n\n\n  handleTodoTitleChanged: (domainEvent) -\u003e\n    todosReadModel[domainEvent.aggregate.id].title = domainEvent.payload.title\n\n\n  handleTodoFinished: (domainEvent) -\u003e\n    todosReadModel[domainEvent.aggregate.id].isFinished = true\n\n\ntodoContext.addProjection todosProjection\n\n\ntodoCountReadModel = 0\n\ntodoCountProjection =\n\n  initialize: (params, done) -\u003e\n    done()\n\n\n  handleTodoCreated: (domainEvent) -\u003e\n    todoCountReadModel++\n\n\ntodoContext.addProjection todoCountProjection\n```\n\n**Important:** Projections must be added to a context before it is initialized.\n\n\n## Query handlers\n\nQuery handlers define the read side of the application service layer inside an eventric context.\n\n### Definition\n\nQuery handlers are registered the same way command handlers are by passing an object to the context.\n\n```coffeescript\ntodoContext.addQueryHandlers\n\n  getTodoList: (params) -\u003e\n    return todosReadModel\n\n\n  getTodoCount: (params) -\u003e\n    return todoCountReadModel\n```\n\n### Execution\n\nSimilar to command handlers queries can be executed from outside the context after defining them.\n\n```coffeescript\ntodoContext.initialize()\n.then -\u003e\n  todoContext.command 'CreateTodo', title: 'My first todo'\n.then (todoId) -\u003e\n  todoContext.command 'ChangeTodoTitle',\n    todoId: todoId\n    title: 'My first changed todo'\n  .then -\u003e\n    todoContext.command 'FinishTodo',\n      todoId: todoId\n.then -\u003e\n  todoContext.query 'getTodoList', {}\n.then (todoList) -\u003e\n  console.log 'current todos:', todoList\n  todoContext.query 'getTodoCount'\n.then (todoCount) -\u003e\n  console.log 'current todo count:', todoCount\n```\n\n## Stores\n\nThe event store inside a context is responsible for saving domain events and searching them by aggregate id or event name.\n\n### In memory\n\nBy default eventric uses an in memory event store which is mainly useful for demo applications and testing purposes.\n\n### MongoDB\n\nFor actual applications use the mongodb event store to save domain events in a persistent way.\nFirst, install it together with the mongodb module.\n\n`npm install mongodb`\n`npm install eventric-store-mongodb`\n\nThen, before initializing any contexts, connect to the database and set the mongodb event store as eventric store.\n\n```coffeescript\nmongodb = require 'mongodb'\nEventricMongoDBStore = require 'eventric-store-mongodb'\n\nmongodb.MongoClient.connect 'your db url', (error, database) -\u003e\n  eventric.setStore EventricMongoDBStore, dbInstance: database\n\n  # initialize todo context\n```\n\n## Persistent read models\n\nAn event store inside a context only handles domain event persistence (write side of the application).\nSaving persistent read models (or views) are not scope of the eventric framework.\n\nTo illustrate how this can be done the above todo list projection is rewritten to use mongodb as read model store.\n\n```\ntodoListProjection =\n\n  initialize: (params, done) -\u003e\n    database.dropCollection 'todos'\n    .then -\u003e\n      database.collection 'todos'\n    .then (collection) -\u003e\n      @collection = collection\n      done()\n\n\n  handleTodoCreated: (domainEvent) -\u003e\n    @collection.insert\n      id: domainEvent.aggregate.id\n      title: domainEvent.payload.title\n\n\n  handleTodoTitleChanged: (domainEvent) -\u003e\n    @collection.update id: domainEvent.aggregate.id,\n      $set: title: domainEvent.payload.title\n\n\n  handleTodoFinished: (domainEvent) -\u003e\n    @collection.update id: domainEvent.aggregate.id,\n      $set: isFinished: true\n\n\n```\n\n*Note:* The above will cause the read model to be emptied whenever the process is restarted. Consider this a best practice.\n\nThe query handler can be changed accordingly to directly access the mongodb collection.\n\n```coffeescript\ntodoContext.addQueryHandlers\n\n  getTodos: -\u003e\n    database.collection 'todos'\n    .then (collection) -\u003e\n      collection.find({}).toArray()\n\n\n```\n\n## Remotes\n\neventric supports service oriented architectures.\nConsider every context to be a possible standalone (micro)service.\nAll contexts share the same API: commands, queries, domain event handlers and projections.\n\nUse the `Remote` interface in order to communicate between contexts.\n\n\n### In memory\n\nBy default eventric provides an in memory remote which is useful for in-process communication and testing purposes.\n\n```coffeescript\ntodoContext = eventric.remote 'Todo'\ntodoContext.command 'CreateTodo'\n.then (todoId) -\u003e\n  console.log todoId\n```\n\n### Socket.IO\n\nAll previous examples were meant to be executed in a single process on the server side of an application.\nIn order to communicate with a context running on a server from a browser use the Socket.IO remote implementations.\n\nFirst, install the modules together with Socket.IO.\n\n```\nnpm install socket.io\nnpm install eventric-remote-socketio-endpoint\nnpm install eventric-remote-socketio-client\n```\n\nThen, configure eventric on the server side to use Socket.IO as additional remote endpoint.\n\n```coffeescript\nsocketIO = require 'socket.io'\nsocketIORemoteEndpoint  = require 'eventric-remote-socketio-endpoint'\n\nio = socketIO.listen 1234\nsocketIORemoteEndpointOptions =\n  ioInstance: io\nsocketIORemoteEndpoint.initialize socketIORemoteEndpointOptions, -\u003e\n  eventric.addRemoteEndpoint socketIORemoteEndpoint\n```\n\nFinally, include the Socket.IO client and eventric in an html file, configure the remote client and create a remote.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eeventric tutorial\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c!-- fix paths to files --\u003e\n    \u003cscript type=\"text/javascript\" src=\"node_modules/socket.io/node_modules/socket.io-client/socket.io.js\" \u003e\u003c/script\u003e\n    \u003cscript type=\"text/javascript\" src=\"node_modules/eventric/dist/release/eventric.js\" \u003e\u003c/script\u003e\n    \u003cscript type=\"text/javascript\" src=\"node_modules/eventric-remote-socketio-client/dist/release/eventric_remote_socketio_client.js\" \u003e\u003c/script\u003e\n\n    \u003cscript type=\"text/javascript\"\u003e\n      var socket = io.connect('http://localhost:1234');\n      socketIORemoteClient = window['eventric-remote-socketio-client'];\n      socketIORemoteClient.initialize({\n        ioClientInstance: socket\n      })\n      .then(function() {\n        var todoContext = eventric.remote('Todo');\n        todoContext.setClient(socketIORemoteClient);\n\n        todoContext.command('CreateTodo', {title: 'My first todo'})\n        .then(function(todoId) {\n          console.log('Todo created: ', todoId);\n        })\n        .catch(function(error) {\n          console.error('Error creating todo: ', error);\n        });\n      });\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n*Note:* Socket.IO remotes are not limited to browser to server. They can easily be used for server to server communication.\n\n\n### Remote projections\n\nOne major strength of eventric is the possibility to create remote projections (or client side projections).\nThis feature makes it easily possible to create reactive user interfaces in the browser.\n\n```coffeescript\ntodoContext = eventric.remote 'Todo'\ntodoContext.setClient socketIORemoteClient\ntodoProjection =\n\n  initialize: (params, done) -\u003e\n    document.querySelector('body').innerHTML = '\u003ch1\u003eTodos\u003c/h1\u003e\u003cdiv class=\"todos\"\u003e\u003c/div\u003e';\n    done()\n\n\n  handleTodoCreated: (domainEvent) -\u003e\n    todoElement = document.createElement 'div'\n    todoElement.setAttribute 'id', domainEvent.aggregate.id\n    todoElement.innerHTML = domainEvent.payload.title\n    document.querySelector('.todos').appendChild todoElement\n\n\n  handleTodoTitleChanged: (domainEvent) -\u003e\n    document.querySelector(\"[id='#{domainEvent.aggregate.id}']\").innerHTML = domainEvent.payload.title\n\n\n  handleTodoFinished: (domainEvent) -\u003e\n    document.querySelector(\"[id='#{domainEvent.aggregate.id}']\").setAttribute 'style', 'text-decoration: line-through'\n\n\ntodoContext.initializeProjection todoProjection, {}\n```\n\n*Note:* `initializeProjection()` may be renamed to `addProjection()` in order to stay consistent with the context API.\n\n## License\n\nMIT\n\nCopyright (c) 2013-2016 SixSteps Team, efa GmbH\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefacilitation%2Feventric","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fefacilitation%2Feventric","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefacilitation%2Feventric/lists"}