Skip to content

Instantly share code, notes, and snippets.

@ianbattersby
Last active January 14, 2016 11:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianbattersby/ea13adb3bf9671847225 to your computer and use it in GitHub Desktop.
Save ianbattersby/ea13adb3bf9671847225 to your computer and use it in GitHub Desktop.

Update: Part 2 of this is in another gist

Example of a more complicated EventStore projection using GitHub API data

  1. Download EventStore (Mono(SL) vs Windows)
  2. Running EventStore from the command-line (w/projections)
  3. Logging in to the UI
  4. Setting a default ACL (using curl)
  5. Enabling default projections
  • What is a projection?
  1. Pushing data into EventStore
  • TCP vs HTTP
  • Streams
  • Event Types
  • Unique ID’s
  • Category streams
  1. Projections
  • Query vs Projection
  • Example query
    • Count number of events in a stream
    • Count number of events for a user (actor) in a stream
  • Example projections
    • Aggregate streams
    • Partition streams
    • Iterate category streams (w/foreachStream())
    • LinkTo
    • Emit
  • Debugging a projection
  1. Other features to check out
  • Pure HTTP querying (curl)
  • Competing consumers
  • C# API
  • Other language API's

Documentation

Available at EventStore Documentation

Default ACL

settings.js

curl -L -i -d@./settings.js "http://127.0.0.1:2113/streams/%24settings" -H "Content-Type:application/json" -H "ES-EventType: settings" -u "admin:changeit"
curl -i http://127.0.0.1:2113/streams/%24settings -u admin:changeit -H "Accept:application/vnd.eventstore.atom+json"

Enable projections!

Reminder to enable default projections.

Writing Data (using GitHubApi)

npm install node-github

pump.js

pump-unauth.js

Reading Data

curl -i http://127.0.0.1:2113/streams/github-PullRequestEvent -u admin:changeit -H "Accept:application/vnd.eventstore.atom+json"

Tricks & Tips

IntelliSense via 1Prelude.js

/// <reference path="scripts/1Prelude.js" />

Queries & Projections

eventstoreprocessor

Queries

// fromAll() will scan ALL events in EventStore (not metadata events)
fromAll()
  .whenAny(function(state,event) { 
     return null; 
  });
  
// fromStream only includes events from the named stream
fromStream("github-CreateEvent")
  .whenAny(function(state,event) { 
     return null; 
  });

// fromCategory includes events from all streams with category name (e.g. <category>-<category-id>)
// nb: .foreachStream() will not work in 'Query' mode as can't specify partition to display
fromCategory("github")
  .whenAny(function(state,event) { 
     return null; 
  });

// We can filter events EventType using 'when({ .. })'
fromAll()
  .when({
    "CreateEvent": function(state, event) {
      return null;
    }
  });

// We can use built-in keywords for 'when', such as $init
fromAll()
  .when({
    $init: function() {
      return {}
    },
    "CreateEvent": function(state, event) {
      return null;
    }
  });

// We can use state through the sequence of events
fromCategory("github")
  .when({
    $init: function() {
      return { 
          createEventCount: 0,
          pullRequestEventCount: 0
          
      }
    },
    "CreateEvent": function(state, event) {
      state.createEventCount++;
      
      return state;
    },
    "PullRequestEvent": function(state, event) {
      state.pullRequestEventCount++;
      
      return state;
    }
  });

Projections

Note that any projections with linkTo or emit need Emit Enabled ticked!

fromCategory("github")
  .when({
    $init: function() {
      return { 
          createEventCount: 0,
          pullRequestEventCount: 0
          
      }
    },
    "CreateEvent": function(state, event) {
      state.createEventCount++;
      
      return state;
    },
    "PullRequestEvent": function(state, event) {
      state.pullRequestEventCount++;
      
      return state;
    }
  });
# When queries are put into running projections we can query them from the
# command-line using their projection name.

curl -i http://localhost:2113/projection/<projection-name>/state -u admin:changeit
# Example output

$ curl -i http://localhost:2113/projection/proj.github-category-count/state -u admin:changeit
HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
ES-Position: {"$s":{"$ce-github":334775}}
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
Content-Type: application/json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Wed, 25 Nov 2015 16:12:25 GMT
Content-Length: 56
Keep-Alive: timeout=15,max=100

{"createEventCount":39011,"pullRequestEventCount":18612}
// If our projections processes category with .foreachStream() we should be able to query state by category-id
fromCategory('github')
.foreachStream()
.whenAny(function(s,e){
        if (s.count === undefined) s.count = 0;
        
        s.count += 1;
        
        return s;
    });
# The addition of the 'partition' querystring parameter denotes the category partition
# to return state for.

curl -i http://localhost:2113/projection/<projection-name>/state?partition=<stream-name-incl-category-id> -u admin:changeit
# Example output for category-id "CreateEvent"

$ curl -i http://localhost:2113/projection/proj.github-category-state/state?partition=github-CreateEvent -u admin:changeit
HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
ES-Position: {"$s":{"$ce-github":111332}}
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
Content-Type: application/json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Wed, 25 Nov 2015 16:22:37 GMT
Content-Length: 13
Keep-Alive: timeout=15,max=100

{"count":566}
# Example output for category-id "PullRequestEvent"

$ curl -i http://localhost:2113/projection/proj.github-category-state/state?partition=github-PullRequestEvent -u admin:changeit
HTTP/1.1 200 OK
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
ES-Position: {"$s":{"$ce-github":149738}}
Cache-Control: max-age=0, no-cache, must-revalidate
Vary: Accept
Content-Type: application/json; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Wed, 25 Nov 2015 16:22:47 GMT
Content-Length: 13
Keep-Alive: timeout=15,max=100

{"count":270}
// We can process a sequence of events and push out events into a new stream;
// using 'linkTo' will ensure the new stream event is merely a pointer to the
// original.
//
// This projection creates a stream called "github-all" and puts a link to 
// every event from the 'github' category into it.
fromCategory("github")
  .whenAny(function(s, e) {
      linkTo("githubAll", e);
      return s;
  });
// We can also 'emit' events, this literally pushes a new event into the (new) stream,
// it can be any event data, or just a copy of the original.
//
// This example partitions data from the github category, where the EventType is 'WatchEvent', and
// creates a new category stream partition by the ID of the person who 'Watched'.
fromCategory('github')
  .foreachStream()
  .when({
        $init: function() {
           return { }
        },
        "WatchEvent": function(state, event) {
          emit('githubWatchesBy-' + event.data.sender.id, "WatchesByEvent", event);
          return state;
        }
  });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment