SlideShare a Scribd company logo
1 of 83
Events at the tip 
of your fingers 
Using CQRS and event sourcing 
on a mobile environement 
Build Stuff 2014 © Cédric Pontet - Agile Partner 1
www.agilepartner.net www.play14.org 
cpontet@agilepartner.net 
@cpontet 
linkedin.com/pub/cédric-pontet 
google.com/+CédricPontet 
Who am I ? 
Technical lead 
Architecture gardener 
Agile coach 
Build Stuff 2014 © Cédric Pontet - Agile Partner 2
Who are you ? 
Whose role is 
Achitect 
Mobile developer 
iOS 
Android 
Windows Phone 
Windows Mobile 
.NET developer 
Java developer 
Who is familiar with 
DDD 
CQRS 
Event sourcing 
I feel for 
these guys 
Build Stuff 2014 © Cédric Pontet - Agile Partner 3
What seems to be the problem ? 
Implementing a mobile app to help distributing mail and parcels 
Build Stuff 2014 © Cédric Pontet - Agile Partner 4
What is the problem domain ? 
DELIVERING MAIL 
As a mail delivery agent 
I want to track the mail that is going to be 
distributed during my daily delivery tour 
So that customers know what is going on 
with their mail 
DELIVERING PARCELS 
As a parcel delivery agent 
I want to be guided through my daily 
delivery truck tour 
So that parcels get delivered as fast and 
efficiently as possible 
SUPERVISING PARCEL WAREHOUSE 
As a parcel warehouse supervisor 
I want to route the parcels to the right truck 
So that they get delivered on time 
Let’s write some 
user stories… 
Build Stuff 2014 © Cédric Pontet - Agile Partner 5
Why rebuild an existing software ? 
The previous 
version of 
the software 
is outdated 
Data should 
be uploaded 
on server 
more often 
We need to be 
able to customize 
reference data 
without deploying 
Operator 
The GUI is not 
homogenous 
throughout 
the app 
Maintenance is 
getting difficult 
and we are not 
confident when 
we deploy 
There is no clear 
separation of 
user activities 
and concerns 
Developer 
Supervisor/User 
Build Stuff 2014 © Cédric Pontet - Agile Partner 6
Meet the Nautiz X5 
• Professional mobile device 
• 806 MHz CPU 
• 256 MB RAM 
• 480 x 640 touch-screen 
• WiFi / GPRS / Bluetooth / GPS 
• Infra-red scanner 
• Would probably survive a plane crash 
• Or even drowning 
• Operating -20 °C to 55 °C 
Build Stuff 2014 © Cédric Pontet - Agile Partner 7
I am not kidding 
Build Stuff 2014 © Cédric Pontet - Agile Partner 8
But… what are the constraints ? 
3.5 
Need to pay attention to 
Threading 
Memory management 
Battery life 
Connectivity 
Framework API limitations 
Build Stuff 2014 © Cédric Pontet - Agile Partner 9
Meet my new friends 
NEventStore 
Build Stuff 2014 © Cédric Pontet - Agile Partner 10
Why events ? 
Designing software with events 
Build Stuff 2014 © Cédric Pontet - Agile Partner 11
A 
P 
P 
3 roles == 3 modules 
Mail 
Delivery 
Parcel 
Delivery 
Parcel 
Supervision 
Mail 
Delivery 
Agent 
Parcel 
Delivery 
Agent 
Parcel 
Supervisor 
Build Stuff 2014 © Cédric Pontet - Agile Partner 12
Mail delivery 
LOG ON THE APPLICATION 
As a mail delivery agent 
I want to scan the bar code uniquely 
identifying my daily tour 
So that I can initiate the tour and start 
enlisting products to be delivered 
PREPARE DAILY TOUR 
As a mail delivery agent 
I want to scan the bar codes of all the 
products that I have to distribute 
So that I can enlist them as being part of my 
tour 
More user stories… 
SET MAIL STATUS 
As a mail delivery agent 
I want to scan the bar codes of the products 
to distribute to a given customer 
So that I can indicate whether it has indeed 
been delivered or not 
COLLECT MAIL BOX 
As a mail delivery agent 
I want to scan the bar code of a mail box 
So that I can mark it as collected 
Build Stuff 2014 © Cédric Pontet - Agile Partner 13
Event oriented by essence 
Thinking with events thanks to Greg Young 
What can possibly happen to a piece of mail ? 
Mail is delivered to the recipient when at home 
The recipient is notified that he has mail awaiting 
Mail is returned to sender when the recipient is unknown 
Mail is sent back to distrubution to be delivered later 
Mail is routed to a packup station 
Build Stuff 2014 © Cédric Pontet - Agile Partner 14
Eventual consistency is not an issue 
Denormalizers Workflow 
Backend system 
Monitoring 
Devices do not need to know what is going on in the rest of the world 
All they need is to share the same business rules 
Data has to be upload to the server eventually 
Build Stuff 2014 © Cédric Pontet - Agile Partner 15
Designing the domain 
Tour 
+ Code 
+ Date 
Product 
+ Code 
Registered Prime Parcel 
<<Enumeration>> 
TourStatus 
+ Created 
+ Started 
+ Suspended 
+ Completed 
Delivered 
ProductState 
State 
pattern 
Notified ReturnedToSender 
Build Stuff 2014 © Cédric Pontet - Agile Partner 16
Designing the domain with events 
Unfortunately did not know about Event Storming at that time 
Heard about it at Build Stuff 2013 
Thanks @ziobrando 
Just started thinking about 
Domain events 
Commands 
Aggregates 
Build Stuff 2014 © Cédric Pontet - Agile Partner 17
Login Tour 
Tour Code 
Tour 
Created 
Tour Code 
Tour Date 
Login 
Command Aggregate Event 
Tour 
Restored 
Tour Code 
Tour was 
already started 
Build Stuff 2014 © Cédric Pontet - Agile Partner 18
Tour 
Enlist 
product 
Product code 
Product enlisted 
Product code 
Product type 
Enlist products 
Command Aggregate Event 
Build Stuff 2014 © Cédric Pontet - Agile Partner 19
Tour 
Deliver product 
Product code 
Customer name 
Signature 
Product delivered 
Product code 
Product type 
Customer name 
Signature 
Deliver product to customer 
Command Aggregate Event 
Build Stuff 2014 © Cédric Pontet - Agile Partner 20
Notify Tour 
Product code 
Product notified 
Product code 
Notification office 
Notify 
Command Aggregate Event 
Build Stuff 2014 © Cédric Pontet - Agile Partner 21
Tour 
Return to sender 
Product code 
Reason 
Product returned 
Product code 
Reason 
Return product to sender 
Command Aggregate Event 
Build Stuff 2014 © Cédric Pontet - Agile Partner 22
Logout Tour 
Tour 
Completed 
Tour Code 
Logout 
Command Aggregate Event 
Tour 
Suspended 
Tour Code 
Tour still has products 
to be processed 
Build Stuff 2014 © Cédric Pontet - Agile Partner 23
Domain rules : product type * delivery option 
Remis Avisé 
Destinataire Boîte Autre Client absent PackUp 24/24 
Recommandé (+liste) Sign. Sign. Bureau 
Valeur déclarée Sign. ? Bureau 
Signification judiciaire Sign. ? Bureau 
Postenveloppe+ x x x Bureau 
Prime x x x Bureau 
Petit paquet / encombrant x x x Bureau 
Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) Sign. Amazon Sign. Bureau Station 
PackUp 
Retour 
Adresse incorrecte Pas de boîte Refusé Parti Décédé Inconnu du facteur 
Recommandé (+liste) x x x x x x 
Valeur déclarée x 
Signification judiciaire x 
Postenveloppe+ x 
Prime x 
Petit paquet / encombrant x ? ? ? ? ? 
Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) x x x x x x 
PackUp 
Too many possible combinations 
Too many different events 
Distrib. Packup 
Magasin fermé Magasin congé Dévoyé Réexpédition Ordre de garde Adresse à vérifier Dépôt Transfer 
Recommandé (+liste) x x x x ? 
Valeur déclarée ? ? ? ? ? 
Signification judiciaire ? ? ? ? ? 
Postenveloppe+ ? 
Prime ? 
Petit paquet / encombrant ? 
Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) x x x x ? 
PackUp x x ? x x 
Too much complexity 
in the domain 
Build Stuff 2014 © Cédric Pontet - Agile Partner 24
Business rules as reference data 
Can be modified centraly 
No need to recompile or deploy 
Updated on device at each logon 
Drive the UI flow 
Build Stuff 2014 © Cédric Pontet - Agile Partner 25
Business rules as reference data 
public enum ProductStatus 
{ 
Enlisted = 0, 
Delivered = 1, 
Notified = 2, 
ReturnedToSender = 3, 
SentBackToDistribution = 4, 
Collected = 5, 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 26
Business rules as reference data 
Build Stuff 2014 © Cédric Pontet - Agile Partner 27
Business rules as reference data 
public enum DeliveryRequirement 
{ 
None = 0, 
SignatureRequired = 1, 
SignatureAndNameRequired = 2, 
NotificationOfficeRequired = 3, 
PackupStationRequired = 4, 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 28
Simplifying the events : tour status 
Tour Completed 
Aggregate Id 
Tour code 
Device info 
Tour Created 
Aggregate Id 
Tour code 
Device info 
Tour Suspended 
Aggregate Id 
Tour code 
Device info 
Tour Resumed 
Aggregate Id 
Tour code 
Device info 
Tour Started 
Aggregate Id 
Tour code 
Device info 
Build Stuff 2014 © Cédric Pontet - Agile Partner 29
Simplifying the events : before & during tour 
Before tour During tour 
Product enlisted 
Aggregate Id 
Product code 
Product type 
Product processed 
Aggregate Id 
Product code 
Product type 
Processing status 
Delivered to 
Signature 
Mail box 
collected 
Aggregate Id 
Product code 
Product type 
Enlistment cleared 
Aggregate Id 
List of product codes 
Enlistment 
reopened 
Aggregate Id 
Build Stuff 2014 © Cédric Pontet - Agile Partner 30
What does it look like ? 
Explaining the architecture on the device and on server side 
Build Stuff 2014 © Cédric Pontet - Agile Partner 31
Architecture on device 
Event store Tour data Reference data 
Events 
Domain In memory 
read model 
Reference data 
View 
Command bus 
Denormalizers 
ViewModel 
Commands 
Command handlers 
Bar code 
Aggregate Id 
Build Stuff 2014 © Cédric Pontet - Agile Partner 32
Aggregate with event sourcing 
• Tour is the only aggregate 
• Encapsulate its complete life cycle 
• Keeps very little state 
• No Product entity in the domain 
• Just events 
• 1 aggregate == 1 stream of events 
1. Check aggregate invariants 
Verify business rules 
Compute required data 
2. Raise event 
Raise an event containing all required data 
Never change state 
3. Apply event to change state 
Use event data to change state 
Replay when rehydrating the aggregate from 
store 
Build Stuff 2014 © Cédric Pontet - Agile Partner 33
Idempotence 
Can call aggregate methods any 
number of times 
Can send same events any number of 
times 
Can scan same bar code any number 
of times 
f(f(x)) = f(x) 
[Test] 
public void Start_Tour_Is_Idempotent() 
{ 
var tour = Create(); 
tour.Start(); 
Assert.DoesNotThrow(() => tour.Start()); 
Assert.That(tour.GetUncommittedEvents<TourCreated>().Count, Is.EqualTo(1)); 
} 
Method called twice 
Only on event raised 
Build Stuff 2014 © Cédric Pontet - Agile Partner 34
NEventStore 
public interface IStoreEvents : IDisposable 
{ 
A stream is easily opened 
IPersistStreams Advanced { get; } 
IEventStream CreateStream(Guid streamId); 
IEventStream OpenStream(Guid streamId, int minRevision, int maxRevision); 
IEventStream OpenStream(Snapshot snapshot, int maxRevision); 
} 
public interface IEventStream : IDisposable 
{ 
Guid StreamId { get; } 
int StreamRevision { get; } 
int CommitSequence { get; } 
ICollection<EventMessage> CommittedEvents { get; } 
IDictionary<string, object> CommittedHeaders { get; } 
ICollection<EventMessage> UncommittedEvents { get; } 
IDictionary<string, object> UncommittedHeaders { get; } 
void Add(EventMessage uncommittedEvent); 
void CommitChanges(Guid commitId); 
void ClearChanges(); 
} 
A stream is a set of ordered 
event messages 
Event messages are saved within 
a stream as a series of commits 
Build Stuff 2014 © Cédric Pontet - Agile Partner 35
NEventStore commit 
public Commit( 
Guid streamId, int streamRevision, Guid commitId, 
int commitSequence, DateTime commitStamp, 
Dictionary<string, object> headers, 
List<EventMessage> events) 
: this() 
{ 
StreamId = streamId; 
CommitId = commitId; 
StreamRevision = streamRevision; 
CommitSequence = commitSequence; 
CommitStamp = commitStamp; 
Headers = headers ?? new Dictionary<string, object>(); 
Events = events ?? new List<EventMessage>(); 
} Commit contain many events 
Commit is serializable 
No snapshot used 
Build Stuff 2014 © Cédric Pontet - Agile Partner 36
What about server side ? 
Idempotent 
EventStore Reference Data 
Events 
Data delta 
Sync Service 
Atom 
Commit Service 
Commits 
ES RM RD ES RM RD ES RM RD 
International Mail Tracking 
Xml 
Subscribe 
Any other system 
Build Stuff 2014 © Cédric Pontet - Agile Partner 37
ATOM : tour feed 
WebAPI 
Fabrik.Common.WebAPI.AtomPub 
Shamelessly borrowed 
the idea from 
@GetEventStore 
Build Stuff 2014 © Cédric Pontet - Agile Partner 38
ATOM : event feed 
Event metadata 
Product type 
Build Stuff 2014 © Cédric Pontet - Agile Partner 39 
Signature Event type & Product code 
Event date & type 
Product status
Reference data maintenance ASP.NET MCV 5 
Scaffolding 
Sync Fwk 
CRUD 
Don’t really need 
event sourcing there 
Build Stuff 2014 © Cédric Pontet - Agile Partner 40
CAP Theorem 
Consistency  all nodes see the same data at the same time 
Don’t need to see the same data on all devices at the same time 
Each stream of events constitutes an isolated log of tour events 
Share the same reference data that is synced regularly 
Availability  a guarantee that every request receives a response about 
whether it was successful or failed 
Devices don’t need server connectivity to work properly 
Just need to guarantee that all events are evenutally uploaded on server 
Partition tolerance  the system continues to operate despite arbitrary 
message loss or failure of part of the system 
Data is uploaded from devices to 1 server as soon as connectivity is available 
Idempotence allows to send messages several times if needed 
Build Stuff 2014 © Cédric Pontet - Agile Partner 41
Where is my business logic ? 
Designing and implementing the domain 
Build Stuff 2014 © Cédric Pontet - Agile Partner 42
Aggregate 
Base class for 
the aggregate 
public class Tour : AggregateBase 
{ 
private TourMetadata _metadata; 
private TourStatus _status; 
private readonly List<string> _enlistedProducts; 
private readonly List<string> _collectedMailBoxes; 
public void Start() 
{ 
if (IsStarted()) 
CommonDomain 
Idempotence 
return; 
CheckCanStart(); 
RaiseEvent(new TourStarted(Id, _tourCode, _device, DateTime.Now)); 
} 
private bool IsStarted() 
{ 
return _status == TourStatus.Started; 
} 
private void CheckCanStart() 
{ 
if (_status != TourStatus.Created) 
Raising event 
throw new InconsistentStateException(String.Format(Messages.CannotStartTour, _status)); 
} 
private void Apply(TourStarted evt) 
{ 
_status = TourStatus.Started; 
} 
Very little 
state 
Changing state 
Build Stuff 2014 © Cédric Pontet - Agile Partner 43
Testing the domain 
public void Deliver_Product_Then_Notify() 
{ 
string deliveredTo = "Destinataire"; 
Bitmap signature = new Bitmap(15, 15); 
string comment = "This is a comment"; 
string differentComment = "This is a different comment"; 
string location = "Differdange"; 
byte[] imageBytes = signature.ToByteArray(ImageFormat.Png); 
Tour tour = TourDefaultValues.CreateTour() 
.ExpectMatchProduct(ProductDefinitions.Amazon.SampleCode, ProductDefinitions.Amazon.Definition) 
.ExpectProductDeliveryOption(ProductDefinitions.Amazon.Definition, 
DeliveryOptions.Delivered.ToRecipient, DeliveryRequirement.SignatureRequired) 
.ExpectProductDeliveryOption(ProductDefinitions.Amazon.Definition, 
DeliveryOptions.Notified.RecipientIsAbsent, DeliveryRequirement.NotificationOfficeRequired) 
TDD 
.NET 3.5  CF 
Nunit 
NSubstitute 
.ExpectDeliveryCategory(DeliveryOptions.Delivered.ToRecipient, DeliveryOptions.Delivered.Category) 
.ExpectDeliveryCategory(DeliveryOptions.Notified.RecipientIsAbsent, DeliveryOptions.Notified.Category); 
tour.Start(); 
Arrange 
Test utility 
extensions 
Test stubs 
Build Stuff 2014 © Cédric Pontet - Agile Partner 44
Testing the domain (continued) 
tour.ProcessProduct(ProductDefinitions.Amazon.SampleCode, DeliveryOptions.Delivered.ToRecipient, deliveredTo, signature, null, comment); 
tour.ProcessProduct(ProductDefinitions.Amazon.SampleCode, DeliveryOptions.Notified.RecipientIsAbsent, null, null, location, differentComment); 
var events = tour.GetUncommittedEvents<ProductProcessed>(); 
Assert.That(events.Count, Is.EqualTo(2)); 
ProductProcessed evt = events.Last(); 
Assert.That(evt.ProductCode, Is.EqualTo(ProductDefinitions.Amazon.SampleCode)); 
Assert.That(evt.Status.StatusCode, Is.EqualTo(DeliveryOptions.Notified.Category.Code)); 
Assert.That(evt.Status.StatusName, Is.EqualTo(DeliveryOptions.Notified.Category.Name)); 
Assert.That(evt.Status.ReasonCode, Is.EqualTo(DeliveryOptions.Notified.RecipientIsAbsent.Code)); 
Assert.That(evt.Status.ReasonName, Is.EqualTo(DeliveryOptions.Notified.RecipientIsAbsent.Name)); 
Assert.That(evt.DeliveredTo, Is.Null); 
Assert.That(evt.Signature, Is.Null); 
Assert.That(evt.Location, Is.EqualTo(location)); 
Assert.That(evt.Comment, Is.EqualTo(differentComment)); 
} 
Assert Act 
Test utility 
extensions 
Build Stuff 2014 © Cédric Pontet - Agile Partner 45
Process product in aggregate 
CommonDomain 
public void ProcessProduct(string productCode, DeliveryOption deliveryOption, string deliveredTo, Bitmap signature, string location, string comment) 
{ 
CheckCanProcessProduct(); 
string upperCaseProductCode = productCode.ToUpper(); 
ProductDefinition productDefinition = GetProductDefinition(upperCaseProductCode); 
ProductDeliveryOption productDeliveryOption = ReferenceData.ProductDeliveryOption(productDefinition, deliveryOption); 
DeliveryCategory deliveryCategory = ReferenceData.DeliveryCategory(deliveryOption); 
CheckSignature(deliveryOption, signature, productDefinition, productDeliveryOption); 
CheckDeliveredTo(deliveryOption, deliveredTo, productDefinition, productDeliveryOption); 
CheckNotificationOffice(deliveryOption, location, productDefinition, productDeliveryOption); 
ProductType type = new ProductType(productDefinition.Code, productDefinition.Name); 
ProcessingStatus status = new ProcessingStatus(deliveryCategory.Code, deliveryCategory.Name, deliveryOption.Code, deliveryOption.Name); 
string deliveredToUpper = (String.IsNullOrEmpty(deliveredTo)) ? null : deliveredTo.RemoveAccent().ToUpper(); 
byte[] signatureBytes = productDeliveryOption.IsSignatureRequired() ? signature.ToByteArray(ImageFormat.Png) : null; 
string packupStation = GetPackupStationName(productCode, productDefinition, productDeliveryOption); 
string actualLocation = location ?? packupStation; 
RaiseEvent(new ProductProcessed(Id, _metadata, upperCaseProductCode, type, DateTime.Now, 
status, deliveredToUpper, signatureBytes, actualLocation, comment)); 
} 
Check 
Get 
ref data 
Check 
invariants 
Build 
event data 
Raise 
event 
Apply actually 
does nothing 
Build Stuff 2014 © Cédric Pontet - Agile Partner 46
Reference data repository 
public interface IStoreReferenceData 
{ 
void Clear(); 
IEnumerable<ProductDefinition> ProductDefinitions(); 
ProductDefinition ProductDefinition(string code); 
ProductDefinition MatchProduct(string productCode); 
IEnumerable<DeliveryCategory> DeliveryCategories(); 
DeliveryCategory DeliveryCategory(DeliveryOption option); 
DeliveryCategory DeliveryCategory(string code); 
IEnumerable<DeliveryOption> DeliveryOptions(); 
IEnumerable<DeliveryOption> DeliveryOptions(string categoryCode); 
IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(); 
IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(string deliveryOptionCode); 
IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(string productDefinitionCode, string deliveryCategoryCode); 
ProductDeliveryOption ProductDeliveryOption(ProductDefinition productDefinition, DeliveryOption deliveryOption); 
IEnumerable<NotificationOffice> NotificationOffices(string tourCode); 
PackupStation PackupStation(int postalCode); 
} 
Product 
Definition 
Delivery 
Categ 
Delivery 
Options 
Product 
Delivery 
Options 
Build Stuff 2014 © Cédric Pontet - Agile Partner 47
Reference Data 
OpenNETCF.ORM 
[Entity(KeyScheme = KeyScheme.GUID)] 
public class ProductDeliveryOption 
{ 
public static ProductDeliveryOption Create(ProductDefinition productDefinition, DeliveryOption deliveryOption, DeliveryRequirement requirement) 
{ 
return new ProductDeliveryOption { Id = Guid.NewGuid(), ProductDefinitionId = productDefinition.Id, 
DeliveryOptionId = deliveryOption.Id, Requirement = requirement }; 
} 
[Field(IsPrimaryKey = true)] 
public Guid Id { get; set; } 
[Field] 
public Guid ProductDefinitionId { get; set; } 
[Field] 
public Guid DeliveryOptionId { get; set; } 
public DeliveryRequirement Requirement { get; set; } 
[Field(FieldName = "Requirement")] 
public int RequirementValue 
{ 
get { return (int)Requirement; } 
set { Requirement = (DeliveryRequirement)value; } 
} 
public enum DeliveryRequirement 
{ 
None = 0, 
SignatureRequired = 1, 
SignatureAndNameRequired = 2, 
NotificationOfficeRequired = 3, 
PackupStationRequired = 4, 
} 
Attribute to mark 
persisted field 
Trick for enum 
Build Stuff 2014 © Cédric Pontet - Agile Partner 48
Persisting aggregate 
public interface IRepository 
{ 
void Clear(); 
TAggregate GetById<TAggregate>(Guid id) where TAggregate : class, IAggregate; 
TAggregate GetById<TAggregate>(Guid id, int version) where TAggregate : class, IAggregate; 
void Save(IAggregate aggregate, Guid commitId, Action<IDictionary<string, object>> updateHeaders); 
} 
CommonDomain 
Called from 
command handlers 
Build Stuff 2014 © Cédric Pontet - Agile Partner 49
NEventStore + CommonDomain  .NET CF 
• Port for .NET Compact Framework 
• TimerDispatchScheduler 
• IDispatchCommits  bool 
• System.CF 
• Lazy<T> 
• System.Transactions 
• System.Collections.Concurrent 
• System.Threading.Tasks 
Meet the rest of my new friends 
GitHub 
Codeplex 
ILSpy 
Mono.Cecil 
public interface IDispatchCommits : IDisposable 
{ 
bool Dispatch(Commit commit); 
} 
But now .NET is 
open source 
Build Stuff 2014 © Cédric Pontet - Agile Partner 50
Persistence wireup 
public IStoreEvents BuildEventStore() 
{ 
return Wireup.Init() 
.UsingSQLitePersistence(_settings) 
.WithDialect(new NEventStore.Persistence.SqlPersistence.SqlDialects.SqliteDialect()) 
.InitializeStorageEngine() 
.UsingJsonSerialization() 
.Compress() 
.EncryptWith(Encryption.Key) 
.HookIntoPipelineUsing(RootWorkItem.Services.Get<IDenormalizeReadModels>()) 
.Build(); 
} 
Encryption 
Pipeline hook to denormalize 
Json serialization 
OpenNETCF.IoC 
Service Locator 
Build Stuff 2014 © Cédric Pontet - Agile Partner 51
Specifications for enlist in tour 
Feature: Enlist Product 
In order to specify that a product will be part of the tour before it starts 
As a mail delivery agent 
I want to enlist the product 
Scenario: Enlist product of type Registered 
Given the tour 'BT121F' is created 
When I scan a product bar code 'RR123456789LU' 
Then a product of type 'REG' is enlisted in the tour 
Scenario: Enlist product of type Prime 
Given the tour 'BT121F' is created 
When I scan a product bar code 'LY123456789SK' 
Then a product of type 'PRI' is enlisted in the tour 
Scenario: Does not enlist an unknow product type 
Given the tour 'BT121F' is created 
When I scan a product bar code 'xxxxxxxxxxxxxxx' 
Then I get notified that the bar code is not valid 
.NET 3.5  CF 
SpecFlow 
Gherkin 
[Given("the tour (.*) is created")] 
public void GivenTheTourIsCreated(string tourCode) 
{ 
ScenarioContext.Current.Pending(); 
} 
[When("I scan a product bar code (.*)")] 
public void WhenIScanAProductBarCode(string productBarCode) 
{ 
ScenarioContext.Current.Pending(); 
} 
[Then("a product of type (.*) is enlisted in the tour")] 
public void ThenAProductOfTypeIsEnlistedInTheTour(string productType) 
{ 
ScenarioContext.Current.Pending(); 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 52
Testing on device 
[Test] 
public void Save_And_Restore_ProductDelivered() 
{ 
ProcessingStatus status = new ProcessingStatus("Delivered", "Remis", "ToRecipient", "Destinataire"); 
string deliveredTo = "Destinataire"; 
byte[] signature = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 
string location = "Differdange"; 
string comment = "This is a comment"; 
ProductProcessed evt = new ProductProcessed(AggregateId, ProductCode, new ProductType(ProductTypeCode, ProductTypeName), 
DateTime.Now, status, deliveredTo, signature, location, comment); 
Save_And_Restore_ProductEvent(evt); 
Assert.That(evt.Status.StatusCode, Is.EqualTo(status.StatusCode)); 
Assert.That(evt.Status.StatusName, Is.EqualTo(status.StatusName)); 
Assert.That(evt.Status.ReasonCode, Is.EqualTo(status.ReasonCode)); 
Assert.That(evt.Status.ReasonName, Is.EqualTo(status.ReasonName)); 
Assert.That(evt.DeliveredTo, Is.EqualTo(deliveredTo)); 
Assert.That(evt.Signature, Is.EqualTo(signature)); 
Assert.That(evt.Location, Is.EqualTo(location)); 
Assert.That(evt.Comment, Is.EqualTo(comment)); 
} 
.NET CF 
NUnitLite 
Saving and restoring the 
ProductProcessed event 
from the actual SQLite store 
Build Stuff 2014 © Cédric Pontet - Agile Partner 53
Running the tests on device 
public class Program 
{ 
static void Main(string[] args) 
{ 
string codeBase = Assembly.GetExecutingAssembly().GetName().CodeBase; 
string output = Path.ChangeExtension(codeBase, "txt"); 
var arguments = new List<string>(); 
arguments.Add("-result:" + codeBase.Replace(".exe", "-result.xml")); 
arguments.Add("-out:" + output); 
arguments.Add("-exclude:" + TestCategories.LongRunning + "," + TestCategories.TimeDepedent); 
new TextUI().Execute(arguments.ToArray()); 
Process.Start(output, String.Empty); 
} 
} 
.NET CF 
NUnitLite 
Thank you 
NUnitLite 
Build Stuff 2014 © Cédric Pontet - Agile Partner 54
How do I interact with the system ? 
Sending commands 
Build Stuff 2014 © Cédric Pontet - Agile Partner 55
Sending commands 
public interface ISendCommands 
{ 
void Send<TCommand>(TCommand command) where TCommand : Command; 
} 
private void SendLoginCommand() 
{ 
_commandBus.Send(new LoginCommand(_login)); 
} 
private void SendCollectMailCommand(string productCode) 
{ 
Generic 
Concrete command 
_commandBus.Send(new CollectMailBoxCommand(_readModelRepository.Current.AggregateId, productCode)); 
} 
Concrete command 
Build Stuff 2014 © Cédric Pontet - Agile Partner 56
Handling commands 
public interface IHandleCommand<TCommand> where TCommand : Command 
{ 
void Handle(TCommand command); 
} 
public class CollectMailBoxCommandHandler : IHandleCommand<CollectMailBoxCommand> 
{ 
public void Handle(CollectMailBoxCommand command) 
{ 
Tour tour = _repository.GetById<Tour>(command.AggregateId); 
Ensure.NotNull(tour, String.Format(Messages.TourNotFound, command.AggregateId)); 
tour.Start(); 
tour.CollectMailBox(command.MailBoxCode); 
_repository.Save(tour, Guid.NewGuid()); 
} 
} 
Implement generic interface 
Implement handle method 
Build Stuff 2014 © Cédric Pontet - Agile Partner 57
Registering handlers 
class AtStartup : IRegisterComponents 
{ 
private readonly IRegisterHandlers _commandBus; 
OpenNETCF.IoC injection 
[InjectionConstructor] 
public AtStartup( 
[ServiceDependency] IRegisterHandlers commandBus, 
[ServiceDependency] IBootstrapModules bootstrapper) 
{ 
_commandBus = commandBus; 
bootstrapper.RegisterBootstrap(ModuleNames.MailDelivery, Bootstrap); 
bootstrapper.RegistrerCleanup(ModuleNames.MailDelivery, Cleanup); 
} 
private void Bootstrap() 
{ 
_commandBus.RegisterHandler( 
Register at bootstrap 
RootWorkItem.Services.GetOrCreate<LoginCommandHandler>()); 
_commandBus.RegisterHandler( 
RootWorkItem.Services.GetOrCreate<CollectMailBoxCommandHandler>()); 
_commandBus.RegisterHandler( 
RootWorkItem.Services.GetOrCreate<ProcessProductsCommandHandler>()); 
} 
private void Cleanup() 
{ 
_commandBus.UnregisterHandlers<LoginCommand>(); 
_commandBus.UnregisterHandlers<CollectMailBoxCommand>(); 
_commandBus.UnregisterHandlers<ProcessProductsCommand>(); 
} 
Unregister at cleanup 
Build Stuff 2014 © Cédric Pontet - Agile Partner 58
Registering handlers in command bus 
All commands 
public interface IRegisterHandlers 
{ 
void RegisterPreExecutionHandler(IHandleCommand<Command> handler); 
void RegisterHandler<TCommand>(IHandleCommand<TCommand> handler) where TCommand : Command; 
void RegisterPostExecutionHandler(IHandleCommand<Command> handler); 
void UnregisterHandlers<TCommand>() where TCommand : Command; 
} 
Specific command 
All commands 
Build Stuff 2014 © Cédric Pontet - Agile Partner 59
Sending commands with command bus 
public class CommandBus : ISendCommands, IRegisterHandlers 
{ 
public void Send<TCommand>(TCommand command) where TCommand : Command 
{ 
CheckHandlersExist<TCommand>(); 
CallPreExecutionHandlers(command); 
CallHandlers(command); 
CallPostExecutionHandlers(command); 
} 
} 
Execute all handlers 
Build Stuff 2014 © Cédric Pontet - Agile Partner 60
What do I see on screen ? 
Building a read model in memory 
Build Stuff 2014 © Cédric Pontet - Agile Partner 61
Denormalizing events 
public interface IDenormalizeDomainEvent<TEvent> 
{ 
void Denormalize(TEvent@event); 
} 
Implement generic interface 
public class ProductProcessedDenormalizer : IDenormalizeDomainEvent<ProductProcessed> 
{ 
public void Denormalize(ProductProcessed@event) 
{ 
TourReadModel readModel = GetTourReadModel(@event.AggregateId); 
ProductDefinition definition = GetProductDefinition(@event.ProductType.Code); 
ProductStatus status = GetProductStatus(@event.Status.StatusCode); 
readModel.ProductType(definition) 
.Product(@event.ProductCode) 
.Process(status, @event.Occurred, @event.Status.ReasonName, @event.DeliveredTo, @event.Location, @event.Comment); 
} 
} 
Use event data to 
update read model 
Build Stuff 2014 © Cédric Pontet - Agile Partner 62
PipelineHook 
public interface IDenormalizeReadModels : IPipelineHook 
{ 
void Clear(); 
void Register<TEvent>(Action<TEvent> action); 
} 
public class DenormalizerPipelineHook : IDenormalizeReadModels 
{ 
private Dictionary<Type, Action<object>> _denormalizers; 
private List<Guid> _denormalized; 
public void PostCommit(Commit committed) 
{ 
Denormalize(committed); 
} 
public Commit Select(Commit committed) 
{ 
Denormalize(committed); 
return committed; 
} 
private void Denormalize(Commit commit) 
{ 
if (_denormalized.Contains(commit.CommitId)) 
return; 
Denormalize(commit.Events); 
_denormalized.Add(commit.CommitId); 
} 
NEventStore 
private void Denormalize(IEnumerable<EventMessage> events) 
{ 
foreach (var eventMessage in events) 
{ 
var action = GetDenormalizerAction(eventMessage.Body); 
if (action == null) 
continue; 
action(eventMessage.Body); 
} 
} 
Actions to 
denormalize 
public void Register<TEvent>(Action<TEvent> action) 
{ 
_denormalizers.Add(typeof(TEvent), obj => action((TEvent)obj)); 
} 
Denormalize when 
saving events 
Denormalize when 
reloading events 
Build Stuff 2014 © Cédric Pontet - Agile Partner 63
Registering denormalizers 
class AtStartup : IRegisterComponents 
{ 
public void Run() 
{ 
RegisterDenormalizers(); 
} 
private void RegisterDenormalizers() 
{ 
RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ClearedProductsFromTourDenormalizer>(); 
RootWorkItem.Services.AddNew<ReadModels.Denormalizers.MailBoxCollectedDenormalizer>(); 
RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ProductProcessedDenormalizer>(); 
RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ProductRegisteredInTourDenormalizer>(); 
RootWorkItem.Services.AddNew<ReadModels.Denormalizers.TourStatusDenormalizer>(); 
} 
} 
OpenNETCF.IoC 
Add denormalizers as 
services 
Build Stuff 2014 © Cédric Pontet - Agile Partner 64
Persisted Read Model 
OpenNETCF.ORM 
[Entity(KeyScheme.GUID, NameInStore = "Tour")] 
public class TourData 
{ 
public static TourData Create(Guid aggregateId, string tourCode, DateTime date) 
{ 
return new TourData { 
AggregateId = aggregateId, 
Code = tourCode, 
DateTime = date.Date }; 
} 
[Field(IsPrimaryKey = true)] 
public Guid AggregateId { get; set; } 
[Field] 
public string Code { get; set; } 
[Field] 
public DateTime DateTime { get; set; } 
} 
public interface IStoreReadModels 
{ 
TourReadModel Current { get; } 
bool IsLoaded { get; } 
void Create(Guid aggregateId, string tourCode, DateTime dateTime); 
Guid? GetTourId(string tourCode, DateTime dateTime); 
void Clear(); 
Build Stuff 2014 © Cédric Pontet - Agile Partner 65 
} 
Only used to retrieve 
aggregate id from 
tour code and date
How do I make it pretty ? 
Making things look nice on screen 
Build Stuff 2014 © Cédric Pontet - Agile Partner 66
Views 
Resco MobileForms Toolkit 2014 
Dock Summary Enlisting Processing 
Build Stuff 2014 © Cédric Pontet - Agile Partner 67
Views (continued) 
Resco MobileForms Toolkit 2014 
Delivery option Signature Message Synchronizing 
Build Stuff 2014 © Cédric Pontet - Agile Partner 68
Registering views 
class AtStartup : IRegisterComponents 
{ 
Views are considered as SmartParts 
public void Run() 
{ 
RootWorkItem.SmartParts.AddNew<DockView>(ViewNames.Dock); 
RootWorkItem.SmartParts.AddNew<DefaultView>(ViewNames.Default); 
} 
class AtStartup : IRegisterComponents 
{ 
public void Run() 
{ 
Constants to identify view uniquely 
//Order is important here since dependencies exist between components 
RootWorkItem.SmartParts.AddNew<HomeView>(ViewNames.MailDelivery.Home); 
RootWorkItem.SmartParts.AddNew<PreTourView>(ViewNames.MailDelivery.PreTour); 
RootWorkItem.SmartParts.AddNew<TourView>(ViewNames.MailDelivery.Tour); 
RootWorkItem.SmartParts.AddNew<InfoView>(ViewNames.MailDelivery.Info); 
RootWorkItem.SmartParts.AddNew<MainView>(ViewNames.MailDelivery.Main); 
} 
} 
OpenNETCF.IoC.UI 
Constants to identify module views 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 69
Registering controls 
class AtStartup : IRegisterComponents 
{ 
OpenNETCF.IoC.UI 
public void Run() 
{ 
RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.Login); 
RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.TourScan); 
RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.DeliveryScan); 
RootWorkItem.SmartParts.AddNew<ServerConnectivityControl>(ControlNames.ServerConnectivity); 
RootWorkItem.SmartParts.AddNew<BluetoothConnectivity>(ControlNames.BlueToothConnectivity); 
RootWorkItem.SmartParts.AddNew<DateTimeControl>(ControlNames.DateTime); 
RootWorkItem.SmartParts.AddNew<DateTimeControl>(ControlNames.DateTimeDock); 
RootWorkItem.SmartParts.AddNew<BatteryControl>(ControlNames.Battery); 
RootWorkItem.SmartParts.AddNew<BatteryControl>(ControlNames.BatteryDock); 
RootWorkItem.SmartParts.AddNew<GpsControl>(ControlNames.Gps); 
RootWorkItem.SmartParts.AddNew<GprsControl>(ControlNames.Gprs); 
RootWorkItem.SmartParts.AddNew<ToolbarControl>(ControlNames.DefaultToolbar).Initialize(ControlNames.DefaultToolbar); 
RootWorkItem.SmartParts.AddNew<OptionListControl>(ControlNames.OptionList); 
RootWorkItem.SmartParts.AddNew<SignatureControl>(ControlNames.Signature); 
RootWorkItem.SmartParts.AddNew<NotificationOfficeListControl>(ControlNames.NotificationOffice); 
RootWorkItem.SmartParts.AddNew<HelpControl>(ControlNames.Help); 
} 
Constants to identify controls uniquely 
Specific initialization 
Build Stuff 2014 © Cédric Pontet - Agile Partner 70
Workspaces 
public partial class DockView : SmartPart 
{ 
[InjectionConstructor] 
public DockView( 
[CreateNew] DockViewModel viewModel, 
[ServiceDependency] INavigateViews navigator, 
[ServiceDependency] IEventAggregator aggregator) 
: this() 
{ 
_scanControl = _scanLoginWorkspace.Register(RootWorkItem.SmartParts[ControlNames.Login]); 
((IValidateInput)_scanControl).Initalize(Messages.LoginInvite, true, Login); 
OpenNETCF.IoC.UI 
View inherit from SmartPart 
Use the constants to retrieve 
the control instance 
_wirelessStrength = _wirelessStrengthWorkspace.Register(RootWorkItem.SmartParts[ControlNames.WirelessStrength 
+ WirelessStrengthImageSize.Large]); 
_serverConnectivity = _connectivityWorkspace.Register(RootWorkItem.SmartParts[ControlNames.ServerConnectivity]); 
_dateTimeWorkspace.Register(RootWorkItem.SmartParts[ControlNames.DateTimeDock]); 
_batteryWorkspace.Register(RootWorkItem.SmartParts[ControlNames.BatteryDock]); 
} 
public override void OnActivated() 
{ 
_scanControl.OnActivated(); 
_wirelessStrength.OnActivated(); 
_serverConnectivity.OnActivated(); 
} 
A workspace is a visual placeholder 
where a control is registered 
Build Stuff 2014 © Cédric Pontet - Agile Partner 71
Event aggregator 
public interface IHandleEvent {} 
public interface IHandleEvent<TMessage> : IHandleEvent 
{ 
void Handle(TMessage message); 
} 
public interface IEventAggregator 
{ 
Not a domain event, 
but UI events 
Action<Action> PublicationThreadMarshaller { get; set; } 
void Subscribe(object instance); 
void Unsubscribe(object instance); 
void Publish(object message); 
void Publish(object message, Action<Action> marshal); 
} 
Micro.EventAggregator 
public class RegisterUICommandEvent 
{ 
public partial class ToolbarControl : SmartPart, 
IHandleEvent<RegisterUICommandEvent>, 
IHandleEvent<RaiseCanExecuteEvent> 
{ 
public new void Handle(RegisterUICommandEvent message) {} 
public new void Handle(RaiseCanExecuteEvent message) {} 
} 
public RegisterUICommandEvent(string toolbarName, IUICommand command) 
{ 
ToolbarName = toolbarName; 
Command = command; 
} 
public string ToolbarName { get; private set; } 
public IUICommand Command { get; private set; } 
} 
A SmartPart can handle 
several events 
Build Stuff 2014 © Cédric Pontet - Agile Partner 72
What about low level stuff ? 
Getting closer to the bare metal 
Build Stuff 2014 © Cédric Pontet - Agile Partner 73
Bar code patterns 
public interface IMonitorBarcodes : IDisposable 
{ 
void Register(IHandleBarcodes barCodeHandler); 
void Unregister(IHandleBarcodes barCodeHandler); 
} 
public interface IHandleBarcodes 
{ 
void SetData(string barcode); 
} 
WindowsCE MessageWindow 
Handheld.NAUTIZ API 
Regex 
Read bar code from 
scanner and publish it 
Register / ungegister 
To be implemented by the 
bar code consumer 
handler 
^(?<year>d{2})(?<ip>(?!A)(w{2}))(?<number>d{6})(?<code>[9])(?<postalcode>d{4})$ 
(^R(?!S)([A-Z])(d{9})([A-Z]{2})$)|(^RS(d{9})SK$) 
^RS(w{9})LU$ 
^B(?<postalcode>d{4})(?<serial>d{3})$ 
Regex patterns to 
match bar codes 
Build Stuff 2014 © Cédric Pontet - Agile Partner 74
Testing bar code patterns 
TDD 
.NET 3.5  CF 
NUnit 
[Test] 
public void Amazon() 
{ 
//Amazon product 
AssertAllPatterns(true, "13A100000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatternsBut(false, "13A100000039999", ProductDefinitions.Amazon.Pattern); 
//Amazon packup 
AssertAllPatterns(false, "13A100000080999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "X3A100000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "1XA100000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13B100000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "130100000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A1000000A9999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A100000009999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A10000003X999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A100000039X99", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A1000000399X9", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A10000003999X", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A1000000399990", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A10000003999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A10000039999", ProductDefinitions.Amazon.Pattern); 
AssertAllPatterns(false, "13A1000000039999", ProductDefinitions.Amazon.Pattern); 
} 
private void AssertAllPatterns(bool expected, string value, 
params string[] patterns) 
{ 
foreach (var pattern in patterns) 
{ 
Assert.AreEqual( 
expected, 
new Regex(pattern).IsMatch(value), 
String.Format("'{0}' {1} '{2}' : {3}", 
value, 
expected ? "does not match" : "matches", 
Patterns[pattern], pattern 
) 
); 
} 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 75
WiFi management 
public void EnableWireless() 
{ 
if (IsWirelessEnabled) 
return; 
this.Log().Info(InfrastructureMessages.SwitchingWirelessOn); 
try 
{ 
var radio = GetRadio(); 
radio.RadioState = RadioState.On; 
} 
catch (Exception e) 
{ 
this.Log().Error(InfrastructureMessages.WirelessError, e); 
throw; 
} 
} 
OpenNETCF.WindowsMobile 
private IRadio GetRadio() 
{ 
try 
{ 
return Radios.GetRadios().FirstOrDefault(r => r.RadioType == RadioType.WiFi); 
} 
catch (Exception) 
{ 
throw new NetworkException(InfrastructureMessages.NoWirelessRadioFound); 
} 
} 
Build Stuff 2014 © Cédric Pontet - Agile Partner 76
Vibrate 
public void Vibrate(int milliseconds) 
{ 
OpenNETCF.WindowsCE 
Use ThreadPool 
whenever you can 
ThreadPool.QueueUserWorkItem(newWaitCallback(x => 
{ 
OpenNETCF.WindowsCE.Notification.Led vib = new OpenNETCF.WindowsCE.Notification.Led(); 
try 
{ 
vib.SetLedStatus(3, OpenNETCF.WindowsCE.Notification.Led.LedState.On); 
System.Threading.Thread.Sleep(milliseconds); 
} 
finally 
{ 
vib.SetLedStatus(3, OpenNETCF.WindowsCE.Notification.Led.LedState.Off); 
} 
})); 
} 
WTF ??? 
LED == vibrate 
Build Stuff 2014 © Cédric Pontet - Agile Partner 77
So what ? 
Bringing it all together 
Build Stuff 2014 © Cédric Pontet - Agile Partner 78
Advantages of events 
Scalability 
Events are very simple and self contained 
Server receives events when device has connectivity 
Concurrency 
Never need to access the events from other devices 
Except reference data, everything is written only once 
Simplicity 
Only one aggregate 
The rest is only events 
Testability 
Unit tests are easy to write 
BDD can be used 
Build Stuff 2014 © Cédric Pontet - Agile Partner 79
Pay attention 
Wording and naming is essential 
Tense is important  events are in the past tense 
Difficult to change events names once in production 
Know your plateform limitations 
Threading, memory, etc. 
Libraries and framework availability 
Expect the dreaded NotImplementedException from .Net CF 
Get ready to draw stuff yourself if you like it nice 
Low level stuff is tricky 
Use libraries and be clever about it 
Get familiar with native code / Pinvoke http://www.pinvoke.net 
TDD helps a great deal 
Build Stuff 2014 © Cédric Pontet - Agile Partner 80
Tips & tricks about .NET CF 
First thing to do when working with CF in VS2008 
C:WindowsMicrosoft.NETFrameworkv3.5Microsoft.CompactFramework.Common.targets 
<Target 
Name="PlatformVerificationTask" Condition="'$(Configuration)' != 'Debug'"> 
<PlatformVerificationTask 
PlatformFamilyName="$(PlatformFamilyName)" 
PlatformID="$(PlatformID)" 
SourceAssembly="@(IntermediateAssembly)" 
ReferencePath="@(ReferencePath)" 
TreatWarningsAsErrors="$(TreatWarningsAsErrors)" 
PlatformVersion="$(TargetFrameworkVersion)"/> 
</Target> 
Follow the white rabbit 
This stuff will save you 
some precious time 
Get ready to dig deep and get your hands dirty 
What are the most valuable .Net Compact Framework Tips, Tricks, and Gotcha-Avoiders? 
Build Stuff 2014 © Cédric Pontet - Agile Partner 81
Links 
• My GitHub 
• https://github.com/cpontet 
• NEventStore 
• http://neventstore.org 
• https://github.com/NEventStore 
• SQLite 
• System.Data.Sqlite 
• OpenNETCF 
• http://www.opennetcf.com 
• https://ioc.codeplex.com 
• Resco MobileForms Toolkit 
• http://www.resco.net 
• Nunit 
• http://www.nunit.org 
• http://nunitlite.org 
• SpecFlow 
• http://www.specflow.org 
• Thanks to Icon8 for the icons 
• http://icons8.com 
Build Stuff 2014 © Cédric Pontet - Agile Partner 82
Thank you for coming 
Special thanks to 
Don’t forget 
March 27-29 2015 
Build Stuff 2014 © Cédric Pontet - Agile Partner 83

More Related Content

Similar to Events at the tip of your fingers

Rewardenv by ITG Cloud - Meet Magento India
Rewardenv by ITG Cloud - Meet Magento IndiaRewardenv by ITG Cloud - Meet Magento India
Rewardenv by ITG Cloud - Meet Magento IndiaPiyushDankhra
 
Grouplink Appreciation Dinner Presentation
Grouplink Appreciation Dinner PresentationGrouplink Appreciation Dinner Presentation
Grouplink Appreciation Dinner PresentationGroupLink
 
Smarter Experimentation with Fully Integrated Data
Smarter Experimentation with Fully Integrated DataSmarter Experimentation with Fully Integrated Data
Smarter Experimentation with Fully Integrated DataOptimizely
 
Vehicle Tracking & Proof Of Delivery (POD) Solution By Detrack
Vehicle Tracking & Proof Of Delivery (POD) Solution By DetrackVehicle Tracking & Proof Of Delivery (POD) Solution By Detrack
Vehicle Tracking & Proof Of Delivery (POD) Solution By DetrackDetrack Systems
 
Behavior driven development - cucumber, Junit and java
Behavior driven development - cucumber, Junit and javaBehavior driven development - cucumber, Junit and java
Behavior driven development - cucumber, Junit and javaNaveen Kumar Singh
 
myAudience-Count Product Overview
myAudience-Count Product OverviewmyAudience-Count Product Overview
myAudience-Count Product OverviewRhonda Software
 
Dolibarr - Information for developers and partners - devcamp valence 2018
Dolibarr - Information for developers and partners - devcamp valence 2018Dolibarr - Information for developers and partners - devcamp valence 2018
Dolibarr - Information for developers and partners - devcamp valence 2018Laurent Destailleur
 
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63Angel Alberici
 
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...AgileNetwork
 
September Partner Bootcamp
September Partner BootcampSeptember Partner Bootcamp
September Partner BootcampAcquia
 
Heng thida slide-final
Heng thida slide-finalHeng thida slide-final
Heng thida slide-finalNeo Bidam
 
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...AppDynamics
 
Company presentation english 1 2015
Company presentation english 1 2015Company presentation english 1 2015
Company presentation english 1 2015Locanisag
 
Preparing_for_PCA_Workbook.pptx
Preparing_for_PCA_Workbook.pptxPreparing_for_PCA_Workbook.pptx
Preparing_for_PCA_Workbook.pptxmichaeljayaraj1
 
Velocity 2014 Tool Chain Choices
Velocity 2014 Tool Chain ChoicesVelocity 2014 Tool Chain Choices
Velocity 2014 Tool Chain ChoicesMark Sigler
 
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...All about engagement with Universal Analytics @ Google Developer Group NYC Ma...
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...Nico Miceli
 
Visartech Company Profile
Visartech Company ProfileVisartech Company Profile
Visartech Company ProfileVisartech
 

Similar to Events at the tip of your fingers (20)

Rewardenv by ITG Cloud - Meet Magento India
Rewardenv by ITG Cloud - Meet Magento IndiaRewardenv by ITG Cloud - Meet Magento India
Rewardenv by ITG Cloud - Meet Magento India
 
Partner_Summit.pdf
Partner_Summit.pdfPartner_Summit.pdf
Partner_Summit.pdf
 
Grouplink Appreciation Dinner Presentation
Grouplink Appreciation Dinner PresentationGrouplink Appreciation Dinner Presentation
Grouplink Appreciation Dinner Presentation
 
Smarter Experimentation with Fully Integrated Data
Smarter Experimentation with Fully Integrated DataSmarter Experimentation with Fully Integrated Data
Smarter Experimentation with Fully Integrated Data
 
Lean Software Delivery
Lean Software DeliveryLean Software Delivery
Lean Software Delivery
 
Vehicle Tracking & Proof Of Delivery (POD) Solution By Detrack
Vehicle Tracking & Proof Of Delivery (POD) Solution By DetrackVehicle Tracking & Proof Of Delivery (POD) Solution By Detrack
Vehicle Tracking & Proof Of Delivery (POD) Solution By Detrack
 
Behavior driven development - cucumber, Junit and java
Behavior driven development - cucumber, Junit and javaBehavior driven development - cucumber, Junit and java
Behavior driven development - cucumber, Junit and java
 
myAudience-Count Product Overview
myAudience-Count Product OverviewmyAudience-Count Product Overview
myAudience-Count Product Overview
 
Dolibarr - Information for developers and partners - devcamp valence 2018
Dolibarr - Information for developers and partners - devcamp valence 2018Dolibarr - Information for developers and partners - devcamp valence 2018
Dolibarr - Information for developers and partners - devcamp valence 2018
 
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63
MuleSoft Event Driven Architecture (EDA Patterns in MuleSoft) - VirtualMuleys63
 
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...
Agile Kolkata 2022 - Somanth Chatterjee & Soumen Deb | Managing Innovation wi...
 
LyteSpark
LyteSparkLyteSpark
LyteSpark
 
September Partner Bootcamp
September Partner BootcampSeptember Partner Bootcamp
September Partner Bootcamp
 
Heng thida slide-final
Heng thida slide-finalHeng thida slide-final
Heng thida slide-final
 
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...
PayU's Digital Transformation: Transparency from Dev to Prod, Monitoring Micr...
 
Company presentation english 1 2015
Company presentation english 1 2015Company presentation english 1 2015
Company presentation english 1 2015
 
Preparing_for_PCA_Workbook.pptx
Preparing_for_PCA_Workbook.pptxPreparing_for_PCA_Workbook.pptx
Preparing_for_PCA_Workbook.pptx
 
Velocity 2014 Tool Chain Choices
Velocity 2014 Tool Chain ChoicesVelocity 2014 Tool Chain Choices
Velocity 2014 Tool Chain Choices
 
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...All about engagement with Universal Analytics @ Google Developer Group NYC Ma...
All about engagement with Universal Analytics @ Google Developer Group NYC Ma...
 
Visartech Company Profile
Visartech Company ProfileVisartech Company Profile
Visartech Company Profile
 

Recently uploaded

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfMarharyta Nedzelska
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)jennyeacort
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Mater
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaHanief Utama
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentationvaddepallysandeep122
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 

Recently uploaded (20)

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdf
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
Call Us🔝>༒+91-9711147426⇛Call In girls karol bagh (Delhi)
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)
 
React Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief UtamaReact Server Component in Next.js by Hanief Utama
React Server Component in Next.js by Hanief Utama
 
Advantages of Odoo ERP 17 for Your Business
Advantages of Odoo ERP 17 for Your BusinessAdvantages of Odoo ERP 17 for Your Business
Advantages of Odoo ERP 17 for Your Business
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentation
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort ServiceHot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
Hot Sexy call girls in Patel Nagar🔝 9953056974 🔝 escort Service
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 

Events at the tip of your fingers

  • 1. Events at the tip of your fingers Using CQRS and event sourcing on a mobile environement Build Stuff 2014 © Cédric Pontet - Agile Partner 1
  • 2. www.agilepartner.net www.play14.org cpontet@agilepartner.net @cpontet linkedin.com/pub/cédric-pontet google.com/+CédricPontet Who am I ? Technical lead Architecture gardener Agile coach Build Stuff 2014 © Cédric Pontet - Agile Partner 2
  • 3. Who are you ? Whose role is Achitect Mobile developer iOS Android Windows Phone Windows Mobile .NET developer Java developer Who is familiar with DDD CQRS Event sourcing I feel for these guys Build Stuff 2014 © Cédric Pontet - Agile Partner 3
  • 4. What seems to be the problem ? Implementing a mobile app to help distributing mail and parcels Build Stuff 2014 © Cédric Pontet - Agile Partner 4
  • 5. What is the problem domain ? DELIVERING MAIL As a mail delivery agent I want to track the mail that is going to be distributed during my daily delivery tour So that customers know what is going on with their mail DELIVERING PARCELS As a parcel delivery agent I want to be guided through my daily delivery truck tour So that parcels get delivered as fast and efficiently as possible SUPERVISING PARCEL WAREHOUSE As a parcel warehouse supervisor I want to route the parcels to the right truck So that they get delivered on time Let’s write some user stories… Build Stuff 2014 © Cédric Pontet - Agile Partner 5
  • 6. Why rebuild an existing software ? The previous version of the software is outdated Data should be uploaded on server more often We need to be able to customize reference data without deploying Operator The GUI is not homogenous throughout the app Maintenance is getting difficult and we are not confident when we deploy There is no clear separation of user activities and concerns Developer Supervisor/User Build Stuff 2014 © Cédric Pontet - Agile Partner 6
  • 7. Meet the Nautiz X5 • Professional mobile device • 806 MHz CPU • 256 MB RAM • 480 x 640 touch-screen • WiFi / GPRS / Bluetooth / GPS • Infra-red scanner • Would probably survive a plane crash • Or even drowning • Operating -20 °C to 55 °C Build Stuff 2014 © Cédric Pontet - Agile Partner 7
  • 8. I am not kidding Build Stuff 2014 © Cédric Pontet - Agile Partner 8
  • 9. But… what are the constraints ? 3.5 Need to pay attention to Threading Memory management Battery life Connectivity Framework API limitations Build Stuff 2014 © Cédric Pontet - Agile Partner 9
  • 10. Meet my new friends NEventStore Build Stuff 2014 © Cédric Pontet - Agile Partner 10
  • 11. Why events ? Designing software with events Build Stuff 2014 © Cédric Pontet - Agile Partner 11
  • 12. A P P 3 roles == 3 modules Mail Delivery Parcel Delivery Parcel Supervision Mail Delivery Agent Parcel Delivery Agent Parcel Supervisor Build Stuff 2014 © Cédric Pontet - Agile Partner 12
  • 13. Mail delivery LOG ON THE APPLICATION As a mail delivery agent I want to scan the bar code uniquely identifying my daily tour So that I can initiate the tour and start enlisting products to be delivered PREPARE DAILY TOUR As a mail delivery agent I want to scan the bar codes of all the products that I have to distribute So that I can enlist them as being part of my tour More user stories… SET MAIL STATUS As a mail delivery agent I want to scan the bar codes of the products to distribute to a given customer So that I can indicate whether it has indeed been delivered or not COLLECT MAIL BOX As a mail delivery agent I want to scan the bar code of a mail box So that I can mark it as collected Build Stuff 2014 © Cédric Pontet - Agile Partner 13
  • 14. Event oriented by essence Thinking with events thanks to Greg Young What can possibly happen to a piece of mail ? Mail is delivered to the recipient when at home The recipient is notified that he has mail awaiting Mail is returned to sender when the recipient is unknown Mail is sent back to distrubution to be delivered later Mail is routed to a packup station Build Stuff 2014 © Cédric Pontet - Agile Partner 14
  • 15. Eventual consistency is not an issue Denormalizers Workflow Backend system Monitoring Devices do not need to know what is going on in the rest of the world All they need is to share the same business rules Data has to be upload to the server eventually Build Stuff 2014 © Cédric Pontet - Agile Partner 15
  • 16. Designing the domain Tour + Code + Date Product + Code Registered Prime Parcel <<Enumeration>> TourStatus + Created + Started + Suspended + Completed Delivered ProductState State pattern Notified ReturnedToSender Build Stuff 2014 © Cédric Pontet - Agile Partner 16
  • 17. Designing the domain with events Unfortunately did not know about Event Storming at that time Heard about it at Build Stuff 2013 Thanks @ziobrando Just started thinking about Domain events Commands Aggregates Build Stuff 2014 © Cédric Pontet - Agile Partner 17
  • 18. Login Tour Tour Code Tour Created Tour Code Tour Date Login Command Aggregate Event Tour Restored Tour Code Tour was already started Build Stuff 2014 © Cédric Pontet - Agile Partner 18
  • 19. Tour Enlist product Product code Product enlisted Product code Product type Enlist products Command Aggregate Event Build Stuff 2014 © Cédric Pontet - Agile Partner 19
  • 20. Tour Deliver product Product code Customer name Signature Product delivered Product code Product type Customer name Signature Deliver product to customer Command Aggregate Event Build Stuff 2014 © Cédric Pontet - Agile Partner 20
  • 21. Notify Tour Product code Product notified Product code Notification office Notify Command Aggregate Event Build Stuff 2014 © Cédric Pontet - Agile Partner 21
  • 22. Tour Return to sender Product code Reason Product returned Product code Reason Return product to sender Command Aggregate Event Build Stuff 2014 © Cédric Pontet - Agile Partner 22
  • 23. Logout Tour Tour Completed Tour Code Logout Command Aggregate Event Tour Suspended Tour Code Tour still has products to be processed Build Stuff 2014 © Cédric Pontet - Agile Partner 23
  • 24. Domain rules : product type * delivery option Remis Avisé Destinataire Boîte Autre Client absent PackUp 24/24 Recommandé (+liste) Sign. Sign. Bureau Valeur déclarée Sign. ? Bureau Signification judiciaire Sign. ? Bureau Postenveloppe+ x x x Bureau Prime x x x Bureau Petit paquet / encombrant x x x Bureau Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) Sign. Amazon Sign. Bureau Station PackUp Retour Adresse incorrecte Pas de boîte Refusé Parti Décédé Inconnu du facteur Recommandé (+liste) x x x x x x Valeur déclarée x Signification judiciaire x Postenveloppe+ x Prime x Petit paquet / encombrant x ? ? ? ? ? Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) x x x x x x PackUp Too many possible combinations Too many different events Distrib. Packup Magasin fermé Magasin congé Dévoyé Réexpédition Ordre de garde Adresse à vérifier Dépôt Transfer Recommandé (+liste) x x x x ? Valeur déclarée ? ? ? ? ? Signification judiciaire ? ? ? ? ? Postenveloppe+ ? Prime ? Petit paquet / encombrant ? Colis (Amazon, Redoute, QPackPlus, EPG, UPU, liste) x x x x ? PackUp x x ? x x Too much complexity in the domain Build Stuff 2014 © Cédric Pontet - Agile Partner 24
  • 25. Business rules as reference data Can be modified centraly No need to recompile or deploy Updated on device at each logon Drive the UI flow Build Stuff 2014 © Cédric Pontet - Agile Partner 25
  • 26. Business rules as reference data public enum ProductStatus { Enlisted = 0, Delivered = 1, Notified = 2, ReturnedToSender = 3, SentBackToDistribution = 4, Collected = 5, } Build Stuff 2014 © Cédric Pontet - Agile Partner 26
  • 27. Business rules as reference data Build Stuff 2014 © Cédric Pontet - Agile Partner 27
  • 28. Business rules as reference data public enum DeliveryRequirement { None = 0, SignatureRequired = 1, SignatureAndNameRequired = 2, NotificationOfficeRequired = 3, PackupStationRequired = 4, } Build Stuff 2014 © Cédric Pontet - Agile Partner 28
  • 29. Simplifying the events : tour status Tour Completed Aggregate Id Tour code Device info Tour Created Aggregate Id Tour code Device info Tour Suspended Aggregate Id Tour code Device info Tour Resumed Aggregate Id Tour code Device info Tour Started Aggregate Id Tour code Device info Build Stuff 2014 © Cédric Pontet - Agile Partner 29
  • 30. Simplifying the events : before & during tour Before tour During tour Product enlisted Aggregate Id Product code Product type Product processed Aggregate Id Product code Product type Processing status Delivered to Signature Mail box collected Aggregate Id Product code Product type Enlistment cleared Aggregate Id List of product codes Enlistment reopened Aggregate Id Build Stuff 2014 © Cédric Pontet - Agile Partner 30
  • 31. What does it look like ? Explaining the architecture on the device and on server side Build Stuff 2014 © Cédric Pontet - Agile Partner 31
  • 32. Architecture on device Event store Tour data Reference data Events Domain In memory read model Reference data View Command bus Denormalizers ViewModel Commands Command handlers Bar code Aggregate Id Build Stuff 2014 © Cédric Pontet - Agile Partner 32
  • 33. Aggregate with event sourcing • Tour is the only aggregate • Encapsulate its complete life cycle • Keeps very little state • No Product entity in the domain • Just events • 1 aggregate == 1 stream of events 1. Check aggregate invariants Verify business rules Compute required data 2. Raise event Raise an event containing all required data Never change state 3. Apply event to change state Use event data to change state Replay when rehydrating the aggregate from store Build Stuff 2014 © Cédric Pontet - Agile Partner 33
  • 34. Idempotence Can call aggregate methods any number of times Can send same events any number of times Can scan same bar code any number of times f(f(x)) = f(x) [Test] public void Start_Tour_Is_Idempotent() { var tour = Create(); tour.Start(); Assert.DoesNotThrow(() => tour.Start()); Assert.That(tour.GetUncommittedEvents<TourCreated>().Count, Is.EqualTo(1)); } Method called twice Only on event raised Build Stuff 2014 © Cédric Pontet - Agile Partner 34
  • 35. NEventStore public interface IStoreEvents : IDisposable { A stream is easily opened IPersistStreams Advanced { get; } IEventStream CreateStream(Guid streamId); IEventStream OpenStream(Guid streamId, int minRevision, int maxRevision); IEventStream OpenStream(Snapshot snapshot, int maxRevision); } public interface IEventStream : IDisposable { Guid StreamId { get; } int StreamRevision { get; } int CommitSequence { get; } ICollection<EventMessage> CommittedEvents { get; } IDictionary<string, object> CommittedHeaders { get; } ICollection<EventMessage> UncommittedEvents { get; } IDictionary<string, object> UncommittedHeaders { get; } void Add(EventMessage uncommittedEvent); void CommitChanges(Guid commitId); void ClearChanges(); } A stream is a set of ordered event messages Event messages are saved within a stream as a series of commits Build Stuff 2014 © Cédric Pontet - Agile Partner 35
  • 36. NEventStore commit public Commit( Guid streamId, int streamRevision, Guid commitId, int commitSequence, DateTime commitStamp, Dictionary<string, object> headers, List<EventMessage> events) : this() { StreamId = streamId; CommitId = commitId; StreamRevision = streamRevision; CommitSequence = commitSequence; CommitStamp = commitStamp; Headers = headers ?? new Dictionary<string, object>(); Events = events ?? new List<EventMessage>(); } Commit contain many events Commit is serializable No snapshot used Build Stuff 2014 © Cédric Pontet - Agile Partner 36
  • 37. What about server side ? Idempotent EventStore Reference Data Events Data delta Sync Service Atom Commit Service Commits ES RM RD ES RM RD ES RM RD International Mail Tracking Xml Subscribe Any other system Build Stuff 2014 © Cédric Pontet - Agile Partner 37
  • 38. ATOM : tour feed WebAPI Fabrik.Common.WebAPI.AtomPub Shamelessly borrowed the idea from @GetEventStore Build Stuff 2014 © Cédric Pontet - Agile Partner 38
  • 39. ATOM : event feed Event metadata Product type Build Stuff 2014 © Cédric Pontet - Agile Partner 39 Signature Event type & Product code Event date & type Product status
  • 40. Reference data maintenance ASP.NET MCV 5 Scaffolding Sync Fwk CRUD Don’t really need event sourcing there Build Stuff 2014 © Cédric Pontet - Agile Partner 40
  • 41. CAP Theorem Consistency  all nodes see the same data at the same time Don’t need to see the same data on all devices at the same time Each stream of events constitutes an isolated log of tour events Share the same reference data that is synced regularly Availability  a guarantee that every request receives a response about whether it was successful or failed Devices don’t need server connectivity to work properly Just need to guarantee that all events are evenutally uploaded on server Partition tolerance  the system continues to operate despite arbitrary message loss or failure of part of the system Data is uploaded from devices to 1 server as soon as connectivity is available Idempotence allows to send messages several times if needed Build Stuff 2014 © Cédric Pontet - Agile Partner 41
  • 42. Where is my business logic ? Designing and implementing the domain Build Stuff 2014 © Cédric Pontet - Agile Partner 42
  • 43. Aggregate Base class for the aggregate public class Tour : AggregateBase { private TourMetadata _metadata; private TourStatus _status; private readonly List<string> _enlistedProducts; private readonly List<string> _collectedMailBoxes; public void Start() { if (IsStarted()) CommonDomain Idempotence return; CheckCanStart(); RaiseEvent(new TourStarted(Id, _tourCode, _device, DateTime.Now)); } private bool IsStarted() { return _status == TourStatus.Started; } private void CheckCanStart() { if (_status != TourStatus.Created) Raising event throw new InconsistentStateException(String.Format(Messages.CannotStartTour, _status)); } private void Apply(TourStarted evt) { _status = TourStatus.Started; } Very little state Changing state Build Stuff 2014 © Cédric Pontet - Agile Partner 43
  • 44. Testing the domain public void Deliver_Product_Then_Notify() { string deliveredTo = "Destinataire"; Bitmap signature = new Bitmap(15, 15); string comment = "This is a comment"; string differentComment = "This is a different comment"; string location = "Differdange"; byte[] imageBytes = signature.ToByteArray(ImageFormat.Png); Tour tour = TourDefaultValues.CreateTour() .ExpectMatchProduct(ProductDefinitions.Amazon.SampleCode, ProductDefinitions.Amazon.Definition) .ExpectProductDeliveryOption(ProductDefinitions.Amazon.Definition, DeliveryOptions.Delivered.ToRecipient, DeliveryRequirement.SignatureRequired) .ExpectProductDeliveryOption(ProductDefinitions.Amazon.Definition, DeliveryOptions.Notified.RecipientIsAbsent, DeliveryRequirement.NotificationOfficeRequired) TDD .NET 3.5  CF Nunit NSubstitute .ExpectDeliveryCategory(DeliveryOptions.Delivered.ToRecipient, DeliveryOptions.Delivered.Category) .ExpectDeliveryCategory(DeliveryOptions.Notified.RecipientIsAbsent, DeliveryOptions.Notified.Category); tour.Start(); Arrange Test utility extensions Test stubs Build Stuff 2014 © Cédric Pontet - Agile Partner 44
  • 45. Testing the domain (continued) tour.ProcessProduct(ProductDefinitions.Amazon.SampleCode, DeliveryOptions.Delivered.ToRecipient, deliveredTo, signature, null, comment); tour.ProcessProduct(ProductDefinitions.Amazon.SampleCode, DeliveryOptions.Notified.RecipientIsAbsent, null, null, location, differentComment); var events = tour.GetUncommittedEvents<ProductProcessed>(); Assert.That(events.Count, Is.EqualTo(2)); ProductProcessed evt = events.Last(); Assert.That(evt.ProductCode, Is.EqualTo(ProductDefinitions.Amazon.SampleCode)); Assert.That(evt.Status.StatusCode, Is.EqualTo(DeliveryOptions.Notified.Category.Code)); Assert.That(evt.Status.StatusName, Is.EqualTo(DeliveryOptions.Notified.Category.Name)); Assert.That(evt.Status.ReasonCode, Is.EqualTo(DeliveryOptions.Notified.RecipientIsAbsent.Code)); Assert.That(evt.Status.ReasonName, Is.EqualTo(DeliveryOptions.Notified.RecipientIsAbsent.Name)); Assert.That(evt.DeliveredTo, Is.Null); Assert.That(evt.Signature, Is.Null); Assert.That(evt.Location, Is.EqualTo(location)); Assert.That(evt.Comment, Is.EqualTo(differentComment)); } Assert Act Test utility extensions Build Stuff 2014 © Cédric Pontet - Agile Partner 45
  • 46. Process product in aggregate CommonDomain public void ProcessProduct(string productCode, DeliveryOption deliveryOption, string deliveredTo, Bitmap signature, string location, string comment) { CheckCanProcessProduct(); string upperCaseProductCode = productCode.ToUpper(); ProductDefinition productDefinition = GetProductDefinition(upperCaseProductCode); ProductDeliveryOption productDeliveryOption = ReferenceData.ProductDeliveryOption(productDefinition, deliveryOption); DeliveryCategory deliveryCategory = ReferenceData.DeliveryCategory(deliveryOption); CheckSignature(deliveryOption, signature, productDefinition, productDeliveryOption); CheckDeliveredTo(deliveryOption, deliveredTo, productDefinition, productDeliveryOption); CheckNotificationOffice(deliveryOption, location, productDefinition, productDeliveryOption); ProductType type = new ProductType(productDefinition.Code, productDefinition.Name); ProcessingStatus status = new ProcessingStatus(deliveryCategory.Code, deliveryCategory.Name, deliveryOption.Code, deliveryOption.Name); string deliveredToUpper = (String.IsNullOrEmpty(deliveredTo)) ? null : deliveredTo.RemoveAccent().ToUpper(); byte[] signatureBytes = productDeliveryOption.IsSignatureRequired() ? signature.ToByteArray(ImageFormat.Png) : null; string packupStation = GetPackupStationName(productCode, productDefinition, productDeliveryOption); string actualLocation = location ?? packupStation; RaiseEvent(new ProductProcessed(Id, _metadata, upperCaseProductCode, type, DateTime.Now, status, deliveredToUpper, signatureBytes, actualLocation, comment)); } Check Get ref data Check invariants Build event data Raise event Apply actually does nothing Build Stuff 2014 © Cédric Pontet - Agile Partner 46
  • 47. Reference data repository public interface IStoreReferenceData { void Clear(); IEnumerable<ProductDefinition> ProductDefinitions(); ProductDefinition ProductDefinition(string code); ProductDefinition MatchProduct(string productCode); IEnumerable<DeliveryCategory> DeliveryCategories(); DeliveryCategory DeliveryCategory(DeliveryOption option); DeliveryCategory DeliveryCategory(string code); IEnumerable<DeliveryOption> DeliveryOptions(); IEnumerable<DeliveryOption> DeliveryOptions(string categoryCode); IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(); IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(string deliveryOptionCode); IEnumerable<ProductDeliveryOption> ProductDeliveryOptions(string productDefinitionCode, string deliveryCategoryCode); ProductDeliveryOption ProductDeliveryOption(ProductDefinition productDefinition, DeliveryOption deliveryOption); IEnumerable<NotificationOffice> NotificationOffices(string tourCode); PackupStation PackupStation(int postalCode); } Product Definition Delivery Categ Delivery Options Product Delivery Options Build Stuff 2014 © Cédric Pontet - Agile Partner 47
  • 48. Reference Data OpenNETCF.ORM [Entity(KeyScheme = KeyScheme.GUID)] public class ProductDeliveryOption { public static ProductDeliveryOption Create(ProductDefinition productDefinition, DeliveryOption deliveryOption, DeliveryRequirement requirement) { return new ProductDeliveryOption { Id = Guid.NewGuid(), ProductDefinitionId = productDefinition.Id, DeliveryOptionId = deliveryOption.Id, Requirement = requirement }; } [Field(IsPrimaryKey = true)] public Guid Id { get; set; } [Field] public Guid ProductDefinitionId { get; set; } [Field] public Guid DeliveryOptionId { get; set; } public DeliveryRequirement Requirement { get; set; } [Field(FieldName = "Requirement")] public int RequirementValue { get { return (int)Requirement; } set { Requirement = (DeliveryRequirement)value; } } public enum DeliveryRequirement { None = 0, SignatureRequired = 1, SignatureAndNameRequired = 2, NotificationOfficeRequired = 3, PackupStationRequired = 4, } Attribute to mark persisted field Trick for enum Build Stuff 2014 © Cédric Pontet - Agile Partner 48
  • 49. Persisting aggregate public interface IRepository { void Clear(); TAggregate GetById<TAggregate>(Guid id) where TAggregate : class, IAggregate; TAggregate GetById<TAggregate>(Guid id, int version) where TAggregate : class, IAggregate; void Save(IAggregate aggregate, Guid commitId, Action<IDictionary<string, object>> updateHeaders); } CommonDomain Called from command handlers Build Stuff 2014 © Cédric Pontet - Agile Partner 49
  • 50. NEventStore + CommonDomain  .NET CF • Port for .NET Compact Framework • TimerDispatchScheduler • IDispatchCommits  bool • System.CF • Lazy<T> • System.Transactions • System.Collections.Concurrent • System.Threading.Tasks Meet the rest of my new friends GitHub Codeplex ILSpy Mono.Cecil public interface IDispatchCommits : IDisposable { bool Dispatch(Commit commit); } But now .NET is open source Build Stuff 2014 © Cédric Pontet - Agile Partner 50
  • 51. Persistence wireup public IStoreEvents BuildEventStore() { return Wireup.Init() .UsingSQLitePersistence(_settings) .WithDialect(new NEventStore.Persistence.SqlPersistence.SqlDialects.SqliteDialect()) .InitializeStorageEngine() .UsingJsonSerialization() .Compress() .EncryptWith(Encryption.Key) .HookIntoPipelineUsing(RootWorkItem.Services.Get<IDenormalizeReadModels>()) .Build(); } Encryption Pipeline hook to denormalize Json serialization OpenNETCF.IoC Service Locator Build Stuff 2014 © Cédric Pontet - Agile Partner 51
  • 52. Specifications for enlist in tour Feature: Enlist Product In order to specify that a product will be part of the tour before it starts As a mail delivery agent I want to enlist the product Scenario: Enlist product of type Registered Given the tour 'BT121F' is created When I scan a product bar code 'RR123456789LU' Then a product of type 'REG' is enlisted in the tour Scenario: Enlist product of type Prime Given the tour 'BT121F' is created When I scan a product bar code 'LY123456789SK' Then a product of type 'PRI' is enlisted in the tour Scenario: Does not enlist an unknow product type Given the tour 'BT121F' is created When I scan a product bar code 'xxxxxxxxxxxxxxx' Then I get notified that the bar code is not valid .NET 3.5  CF SpecFlow Gherkin [Given("the tour (.*) is created")] public void GivenTheTourIsCreated(string tourCode) { ScenarioContext.Current.Pending(); } [When("I scan a product bar code (.*)")] public void WhenIScanAProductBarCode(string productBarCode) { ScenarioContext.Current.Pending(); } [Then("a product of type (.*) is enlisted in the tour")] public void ThenAProductOfTypeIsEnlistedInTheTour(string productType) { ScenarioContext.Current.Pending(); } Build Stuff 2014 © Cédric Pontet - Agile Partner 52
  • 53. Testing on device [Test] public void Save_And_Restore_ProductDelivered() { ProcessingStatus status = new ProcessingStatus("Delivered", "Remis", "ToRecipient", "Destinataire"); string deliveredTo = "Destinataire"; byte[] signature = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; string location = "Differdange"; string comment = "This is a comment"; ProductProcessed evt = new ProductProcessed(AggregateId, ProductCode, new ProductType(ProductTypeCode, ProductTypeName), DateTime.Now, status, deliveredTo, signature, location, comment); Save_And_Restore_ProductEvent(evt); Assert.That(evt.Status.StatusCode, Is.EqualTo(status.StatusCode)); Assert.That(evt.Status.StatusName, Is.EqualTo(status.StatusName)); Assert.That(evt.Status.ReasonCode, Is.EqualTo(status.ReasonCode)); Assert.That(evt.Status.ReasonName, Is.EqualTo(status.ReasonName)); Assert.That(evt.DeliveredTo, Is.EqualTo(deliveredTo)); Assert.That(evt.Signature, Is.EqualTo(signature)); Assert.That(evt.Location, Is.EqualTo(location)); Assert.That(evt.Comment, Is.EqualTo(comment)); } .NET CF NUnitLite Saving and restoring the ProductProcessed event from the actual SQLite store Build Stuff 2014 © Cédric Pontet - Agile Partner 53
  • 54. Running the tests on device public class Program { static void Main(string[] args) { string codeBase = Assembly.GetExecutingAssembly().GetName().CodeBase; string output = Path.ChangeExtension(codeBase, "txt"); var arguments = new List<string>(); arguments.Add("-result:" + codeBase.Replace(".exe", "-result.xml")); arguments.Add("-out:" + output); arguments.Add("-exclude:" + TestCategories.LongRunning + "," + TestCategories.TimeDepedent); new TextUI().Execute(arguments.ToArray()); Process.Start(output, String.Empty); } } .NET CF NUnitLite Thank you NUnitLite Build Stuff 2014 © Cédric Pontet - Agile Partner 54
  • 55. How do I interact with the system ? Sending commands Build Stuff 2014 © Cédric Pontet - Agile Partner 55
  • 56. Sending commands public interface ISendCommands { void Send<TCommand>(TCommand command) where TCommand : Command; } private void SendLoginCommand() { _commandBus.Send(new LoginCommand(_login)); } private void SendCollectMailCommand(string productCode) { Generic Concrete command _commandBus.Send(new CollectMailBoxCommand(_readModelRepository.Current.AggregateId, productCode)); } Concrete command Build Stuff 2014 © Cédric Pontet - Agile Partner 56
  • 57. Handling commands public interface IHandleCommand<TCommand> where TCommand : Command { void Handle(TCommand command); } public class CollectMailBoxCommandHandler : IHandleCommand<CollectMailBoxCommand> { public void Handle(CollectMailBoxCommand command) { Tour tour = _repository.GetById<Tour>(command.AggregateId); Ensure.NotNull(tour, String.Format(Messages.TourNotFound, command.AggregateId)); tour.Start(); tour.CollectMailBox(command.MailBoxCode); _repository.Save(tour, Guid.NewGuid()); } } Implement generic interface Implement handle method Build Stuff 2014 © Cédric Pontet - Agile Partner 57
  • 58. Registering handlers class AtStartup : IRegisterComponents { private readonly IRegisterHandlers _commandBus; OpenNETCF.IoC injection [InjectionConstructor] public AtStartup( [ServiceDependency] IRegisterHandlers commandBus, [ServiceDependency] IBootstrapModules bootstrapper) { _commandBus = commandBus; bootstrapper.RegisterBootstrap(ModuleNames.MailDelivery, Bootstrap); bootstrapper.RegistrerCleanup(ModuleNames.MailDelivery, Cleanup); } private void Bootstrap() { _commandBus.RegisterHandler( Register at bootstrap RootWorkItem.Services.GetOrCreate<LoginCommandHandler>()); _commandBus.RegisterHandler( RootWorkItem.Services.GetOrCreate<CollectMailBoxCommandHandler>()); _commandBus.RegisterHandler( RootWorkItem.Services.GetOrCreate<ProcessProductsCommandHandler>()); } private void Cleanup() { _commandBus.UnregisterHandlers<LoginCommand>(); _commandBus.UnregisterHandlers<CollectMailBoxCommand>(); _commandBus.UnregisterHandlers<ProcessProductsCommand>(); } Unregister at cleanup Build Stuff 2014 © Cédric Pontet - Agile Partner 58
  • 59. Registering handlers in command bus All commands public interface IRegisterHandlers { void RegisterPreExecutionHandler(IHandleCommand<Command> handler); void RegisterHandler<TCommand>(IHandleCommand<TCommand> handler) where TCommand : Command; void RegisterPostExecutionHandler(IHandleCommand<Command> handler); void UnregisterHandlers<TCommand>() where TCommand : Command; } Specific command All commands Build Stuff 2014 © Cédric Pontet - Agile Partner 59
  • 60. Sending commands with command bus public class CommandBus : ISendCommands, IRegisterHandlers { public void Send<TCommand>(TCommand command) where TCommand : Command { CheckHandlersExist<TCommand>(); CallPreExecutionHandlers(command); CallHandlers(command); CallPostExecutionHandlers(command); } } Execute all handlers Build Stuff 2014 © Cédric Pontet - Agile Partner 60
  • 61. What do I see on screen ? Building a read model in memory Build Stuff 2014 © Cédric Pontet - Agile Partner 61
  • 62. Denormalizing events public interface IDenormalizeDomainEvent<TEvent> { void Denormalize(TEvent@event); } Implement generic interface public class ProductProcessedDenormalizer : IDenormalizeDomainEvent<ProductProcessed> { public void Denormalize(ProductProcessed@event) { TourReadModel readModel = GetTourReadModel(@event.AggregateId); ProductDefinition definition = GetProductDefinition(@event.ProductType.Code); ProductStatus status = GetProductStatus(@event.Status.StatusCode); readModel.ProductType(definition) .Product(@event.ProductCode) .Process(status, @event.Occurred, @event.Status.ReasonName, @event.DeliveredTo, @event.Location, @event.Comment); } } Use event data to update read model Build Stuff 2014 © Cédric Pontet - Agile Partner 62
  • 63. PipelineHook public interface IDenormalizeReadModels : IPipelineHook { void Clear(); void Register<TEvent>(Action<TEvent> action); } public class DenormalizerPipelineHook : IDenormalizeReadModels { private Dictionary<Type, Action<object>> _denormalizers; private List<Guid> _denormalized; public void PostCommit(Commit committed) { Denormalize(committed); } public Commit Select(Commit committed) { Denormalize(committed); return committed; } private void Denormalize(Commit commit) { if (_denormalized.Contains(commit.CommitId)) return; Denormalize(commit.Events); _denormalized.Add(commit.CommitId); } NEventStore private void Denormalize(IEnumerable<EventMessage> events) { foreach (var eventMessage in events) { var action = GetDenormalizerAction(eventMessage.Body); if (action == null) continue; action(eventMessage.Body); } } Actions to denormalize public void Register<TEvent>(Action<TEvent> action) { _denormalizers.Add(typeof(TEvent), obj => action((TEvent)obj)); } Denormalize when saving events Denormalize when reloading events Build Stuff 2014 © Cédric Pontet - Agile Partner 63
  • 64. Registering denormalizers class AtStartup : IRegisterComponents { public void Run() { RegisterDenormalizers(); } private void RegisterDenormalizers() { RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ClearedProductsFromTourDenormalizer>(); RootWorkItem.Services.AddNew<ReadModels.Denormalizers.MailBoxCollectedDenormalizer>(); RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ProductProcessedDenormalizer>(); RootWorkItem.Services.AddNew<ReadModels.Denormalizers.ProductRegisteredInTourDenormalizer>(); RootWorkItem.Services.AddNew<ReadModels.Denormalizers.TourStatusDenormalizer>(); } } OpenNETCF.IoC Add denormalizers as services Build Stuff 2014 © Cédric Pontet - Agile Partner 64
  • 65. Persisted Read Model OpenNETCF.ORM [Entity(KeyScheme.GUID, NameInStore = "Tour")] public class TourData { public static TourData Create(Guid aggregateId, string tourCode, DateTime date) { return new TourData { AggregateId = aggregateId, Code = tourCode, DateTime = date.Date }; } [Field(IsPrimaryKey = true)] public Guid AggregateId { get; set; } [Field] public string Code { get; set; } [Field] public DateTime DateTime { get; set; } } public interface IStoreReadModels { TourReadModel Current { get; } bool IsLoaded { get; } void Create(Guid aggregateId, string tourCode, DateTime dateTime); Guid? GetTourId(string tourCode, DateTime dateTime); void Clear(); Build Stuff 2014 © Cédric Pontet - Agile Partner 65 } Only used to retrieve aggregate id from tour code and date
  • 66. How do I make it pretty ? Making things look nice on screen Build Stuff 2014 © Cédric Pontet - Agile Partner 66
  • 67. Views Resco MobileForms Toolkit 2014 Dock Summary Enlisting Processing Build Stuff 2014 © Cédric Pontet - Agile Partner 67
  • 68. Views (continued) Resco MobileForms Toolkit 2014 Delivery option Signature Message Synchronizing Build Stuff 2014 © Cédric Pontet - Agile Partner 68
  • 69. Registering views class AtStartup : IRegisterComponents { Views are considered as SmartParts public void Run() { RootWorkItem.SmartParts.AddNew<DockView>(ViewNames.Dock); RootWorkItem.SmartParts.AddNew<DefaultView>(ViewNames.Default); } class AtStartup : IRegisterComponents { public void Run() { Constants to identify view uniquely //Order is important here since dependencies exist between components RootWorkItem.SmartParts.AddNew<HomeView>(ViewNames.MailDelivery.Home); RootWorkItem.SmartParts.AddNew<PreTourView>(ViewNames.MailDelivery.PreTour); RootWorkItem.SmartParts.AddNew<TourView>(ViewNames.MailDelivery.Tour); RootWorkItem.SmartParts.AddNew<InfoView>(ViewNames.MailDelivery.Info); RootWorkItem.SmartParts.AddNew<MainView>(ViewNames.MailDelivery.Main); } } OpenNETCF.IoC.UI Constants to identify module views } Build Stuff 2014 © Cédric Pontet - Agile Partner 69
  • 70. Registering controls class AtStartup : IRegisterComponents { OpenNETCF.IoC.UI public void Run() { RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.Login); RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.TourScan); RootWorkItem.SmartParts.AddNew<ScanControl>(ControlNames.DeliveryScan); RootWorkItem.SmartParts.AddNew<ServerConnectivityControl>(ControlNames.ServerConnectivity); RootWorkItem.SmartParts.AddNew<BluetoothConnectivity>(ControlNames.BlueToothConnectivity); RootWorkItem.SmartParts.AddNew<DateTimeControl>(ControlNames.DateTime); RootWorkItem.SmartParts.AddNew<DateTimeControl>(ControlNames.DateTimeDock); RootWorkItem.SmartParts.AddNew<BatteryControl>(ControlNames.Battery); RootWorkItem.SmartParts.AddNew<BatteryControl>(ControlNames.BatteryDock); RootWorkItem.SmartParts.AddNew<GpsControl>(ControlNames.Gps); RootWorkItem.SmartParts.AddNew<GprsControl>(ControlNames.Gprs); RootWorkItem.SmartParts.AddNew<ToolbarControl>(ControlNames.DefaultToolbar).Initialize(ControlNames.DefaultToolbar); RootWorkItem.SmartParts.AddNew<OptionListControl>(ControlNames.OptionList); RootWorkItem.SmartParts.AddNew<SignatureControl>(ControlNames.Signature); RootWorkItem.SmartParts.AddNew<NotificationOfficeListControl>(ControlNames.NotificationOffice); RootWorkItem.SmartParts.AddNew<HelpControl>(ControlNames.Help); } Constants to identify controls uniquely Specific initialization Build Stuff 2014 © Cédric Pontet - Agile Partner 70
  • 71. Workspaces public partial class DockView : SmartPart { [InjectionConstructor] public DockView( [CreateNew] DockViewModel viewModel, [ServiceDependency] INavigateViews navigator, [ServiceDependency] IEventAggregator aggregator) : this() { _scanControl = _scanLoginWorkspace.Register(RootWorkItem.SmartParts[ControlNames.Login]); ((IValidateInput)_scanControl).Initalize(Messages.LoginInvite, true, Login); OpenNETCF.IoC.UI View inherit from SmartPart Use the constants to retrieve the control instance _wirelessStrength = _wirelessStrengthWorkspace.Register(RootWorkItem.SmartParts[ControlNames.WirelessStrength + WirelessStrengthImageSize.Large]); _serverConnectivity = _connectivityWorkspace.Register(RootWorkItem.SmartParts[ControlNames.ServerConnectivity]); _dateTimeWorkspace.Register(RootWorkItem.SmartParts[ControlNames.DateTimeDock]); _batteryWorkspace.Register(RootWorkItem.SmartParts[ControlNames.BatteryDock]); } public override void OnActivated() { _scanControl.OnActivated(); _wirelessStrength.OnActivated(); _serverConnectivity.OnActivated(); } A workspace is a visual placeholder where a control is registered Build Stuff 2014 © Cédric Pontet - Agile Partner 71
  • 72. Event aggregator public interface IHandleEvent {} public interface IHandleEvent<TMessage> : IHandleEvent { void Handle(TMessage message); } public interface IEventAggregator { Not a domain event, but UI events Action<Action> PublicationThreadMarshaller { get; set; } void Subscribe(object instance); void Unsubscribe(object instance); void Publish(object message); void Publish(object message, Action<Action> marshal); } Micro.EventAggregator public class RegisterUICommandEvent { public partial class ToolbarControl : SmartPart, IHandleEvent<RegisterUICommandEvent>, IHandleEvent<RaiseCanExecuteEvent> { public new void Handle(RegisterUICommandEvent message) {} public new void Handle(RaiseCanExecuteEvent message) {} } public RegisterUICommandEvent(string toolbarName, IUICommand command) { ToolbarName = toolbarName; Command = command; } public string ToolbarName { get; private set; } public IUICommand Command { get; private set; } } A SmartPart can handle several events Build Stuff 2014 © Cédric Pontet - Agile Partner 72
  • 73. What about low level stuff ? Getting closer to the bare metal Build Stuff 2014 © Cédric Pontet - Agile Partner 73
  • 74. Bar code patterns public interface IMonitorBarcodes : IDisposable { void Register(IHandleBarcodes barCodeHandler); void Unregister(IHandleBarcodes barCodeHandler); } public interface IHandleBarcodes { void SetData(string barcode); } WindowsCE MessageWindow Handheld.NAUTIZ API Regex Read bar code from scanner and publish it Register / ungegister To be implemented by the bar code consumer handler ^(?<year>d{2})(?<ip>(?!A)(w{2}))(?<number>d{6})(?<code>[9])(?<postalcode>d{4})$ (^R(?!S)([A-Z])(d{9})([A-Z]{2})$)|(^RS(d{9})SK$) ^RS(w{9})LU$ ^B(?<postalcode>d{4})(?<serial>d{3})$ Regex patterns to match bar codes Build Stuff 2014 © Cédric Pontet - Agile Partner 74
  • 75. Testing bar code patterns TDD .NET 3.5  CF NUnit [Test] public void Amazon() { //Amazon product AssertAllPatterns(true, "13A100000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatternsBut(false, "13A100000039999", ProductDefinitions.Amazon.Pattern); //Amazon packup AssertAllPatterns(false, "13A100000080999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "X3A100000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "1XA100000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13B100000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "130100000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A1000000A9999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A100000009999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A10000003X999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A100000039X99", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A1000000399X9", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A10000003999X", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A1000000399990", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A10000003999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A10000039999", ProductDefinitions.Amazon.Pattern); AssertAllPatterns(false, "13A1000000039999", ProductDefinitions.Amazon.Pattern); } private void AssertAllPatterns(bool expected, string value, params string[] patterns) { foreach (var pattern in patterns) { Assert.AreEqual( expected, new Regex(pattern).IsMatch(value), String.Format("'{0}' {1} '{2}' : {3}", value, expected ? "does not match" : "matches", Patterns[pattern], pattern ) ); } } Build Stuff 2014 © Cédric Pontet - Agile Partner 75
  • 76. WiFi management public void EnableWireless() { if (IsWirelessEnabled) return; this.Log().Info(InfrastructureMessages.SwitchingWirelessOn); try { var radio = GetRadio(); radio.RadioState = RadioState.On; } catch (Exception e) { this.Log().Error(InfrastructureMessages.WirelessError, e); throw; } } OpenNETCF.WindowsMobile private IRadio GetRadio() { try { return Radios.GetRadios().FirstOrDefault(r => r.RadioType == RadioType.WiFi); } catch (Exception) { throw new NetworkException(InfrastructureMessages.NoWirelessRadioFound); } } Build Stuff 2014 © Cédric Pontet - Agile Partner 76
  • 77. Vibrate public void Vibrate(int milliseconds) { OpenNETCF.WindowsCE Use ThreadPool whenever you can ThreadPool.QueueUserWorkItem(newWaitCallback(x => { OpenNETCF.WindowsCE.Notification.Led vib = new OpenNETCF.WindowsCE.Notification.Led(); try { vib.SetLedStatus(3, OpenNETCF.WindowsCE.Notification.Led.LedState.On); System.Threading.Thread.Sleep(milliseconds); } finally { vib.SetLedStatus(3, OpenNETCF.WindowsCE.Notification.Led.LedState.Off); } })); } WTF ??? LED == vibrate Build Stuff 2014 © Cédric Pontet - Agile Partner 77
  • 78. So what ? Bringing it all together Build Stuff 2014 © Cédric Pontet - Agile Partner 78
  • 79. Advantages of events Scalability Events are very simple and self contained Server receives events when device has connectivity Concurrency Never need to access the events from other devices Except reference data, everything is written only once Simplicity Only one aggregate The rest is only events Testability Unit tests are easy to write BDD can be used Build Stuff 2014 © Cédric Pontet - Agile Partner 79
  • 80. Pay attention Wording and naming is essential Tense is important  events are in the past tense Difficult to change events names once in production Know your plateform limitations Threading, memory, etc. Libraries and framework availability Expect the dreaded NotImplementedException from .Net CF Get ready to draw stuff yourself if you like it nice Low level stuff is tricky Use libraries and be clever about it Get familiar with native code / Pinvoke http://www.pinvoke.net TDD helps a great deal Build Stuff 2014 © Cédric Pontet - Agile Partner 80
  • 81. Tips & tricks about .NET CF First thing to do when working with CF in VS2008 C:WindowsMicrosoft.NETFrameworkv3.5Microsoft.CompactFramework.Common.targets <Target Name="PlatformVerificationTask" Condition="'$(Configuration)' != 'Debug'"> <PlatformVerificationTask PlatformFamilyName="$(PlatformFamilyName)" PlatformID="$(PlatformID)" SourceAssembly="@(IntermediateAssembly)" ReferencePath="@(ReferencePath)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" PlatformVersion="$(TargetFrameworkVersion)"/> </Target> Follow the white rabbit This stuff will save you some precious time Get ready to dig deep and get your hands dirty What are the most valuable .Net Compact Framework Tips, Tricks, and Gotcha-Avoiders? Build Stuff 2014 © Cédric Pontet - Agile Partner 81
  • 82. Links • My GitHub • https://github.com/cpontet • NEventStore • http://neventstore.org • https://github.com/NEventStore • SQLite • System.Data.Sqlite • OpenNETCF • http://www.opennetcf.com • https://ioc.codeplex.com • Resco MobileForms Toolkit • http://www.resco.net • Nunit • http://www.nunit.org • http://nunitlite.org • SpecFlow • http://www.specflow.org • Thanks to Icon8 for the icons • http://icons8.com Build Stuff 2014 © Cédric Pontet - Agile Partner 82
  • 83. Thank you for coming Special thanks to Don’t forget March 27-29 2015 Build Stuff 2014 © Cédric Pontet - Agile Partner 83

Editor's Notes

  1. Welcome Thanks for being here Flatered to be here
  2. - Mail/parcel delivery business - Customer prefers to stay anonymous
  3. Manager/User : Outdated Operator : Customize reference data Developer : Maintenance Operator : Data upload during tour (GPRS ?) Manager/User : GUI homogeneity Developer : Modularity / separation
  4. - NEventStore : A persistence agnostic Event Store for .NET. It is a persistence library used to abstract different storage implementations when using event sourcing as storage mechanism - SQLite : small footprint, efficient - OpenNETCF : low level CF stuff (connectivity, OS, etc.), frameworks - Resco : MobileForms Toolkit for grids, tabs, toolbars, etc. - SOTI MobiControl : Enterprise Mobility Management (EMM) and Bring Your Own Device (BYOD) Management solution  deployment packages, retrieve files/logs
  5. - Logon is done on premises, at the mail distribution warehouse - Tour is prepared be each delivery agent by scanning all the products he has to distribute - The agent goes out and starts his tour - He has to distribute all the mail he has and collect some mail boxes on the way
  6. A Tour has Products of different subtypes Registered Prime Parcel etc. A Tour has as status that specifies if it is Started suspended completed A Product has a state that defines whether it has been delivered, notified (client is not there), returned to sender, etc. Seems reasonable ?
  7. - Designing domain with events requires that you think a little bit differently
  8. - Login command with the tour code is sent to the tour - The tour raises an event that it has been created - Or that it has been restored if it had already been started before A tour exists for a given tour code, at a given date
  9. - Enlist command with product code is sent to the tour - The tour checks if the product code is already enlisted - Otherwise it raises a product enlisted event containing the product code and product type - The product type is deducted from the product code
  10. - Deliver product command containing the product code, customer name and signature when needed is sent to the tour - Tour checks that all required information has been provided according to the product type retrieved from the product code - Tour raises an event that product has been delivered to the given customer
  11. - Nofity command containing the product code is sent to the tour - The tour retrieves in which office the product will be notified according to the tour code - The tour raises an event that the prodcut will be notified at the given office
  12. - Return to sender command containing the product code and the reason of the return is sent to the tour - Reasons : incorrect address, no mail box, customer refused, customer deceased, customer gone, unknown by agent - The tour checks that this type of product can be returned to sender and that a reason has been provided - The rour raises an event that the product has been returnred to sender for the given reason
  13. - Logout command is sent to the tour - The tour checks whether all products have been processed - If it’s the case, tour raises an event that it is completed - Otherwise, the tour raises an event that it has been suspended
  14. - Combination of product types and delivery options by category is quite large - In some cases, extra information is required, like signature, customer name or notification office
  15. Product definition : registered mail Prime small or cumbersome parcel Amazon parcel bundle of registered mail
  16. - Delivered : product has been given to the customer - Notified : customer was not there and we notified the he can come to a given office to get his mail - Returned to sender : the mail will not be delivered at all - Sent back to distribution : the mail has to wait to be delivered (hollidays, retention orders) or address needs to be checked - Packup : Routed to a packup station  considered as delivered to customer
  17. - Several options per delivery category - Reasons why the mail is returned to sender - Reasons why the mail is sent back in distrubtion
  18. - Combinations of product types and delivery options - Delivery requirement specifies required data and drives UI behavior
  19. - We still need 5 events to maintain the tour status
  20. - Sync framework to synchronize reference data - No need of event sourcing here because it is mainly CRUD - ES would have been an overkill
  21. TIME  12:15
  22. - TimerDispatchScheduler : could not port AsyncDispatchScheduler because it is based on concurrent queues that does not exist in CF - IDispatchCommits : needed a bool to check if events were dispatched properly, did not want to use exceptions for that - System.CF : Had to reimplement some parts of the .NET Fx
  23. - Encryption : legal obligation - Pipeline hook : denormalize in memory, cannot have 2 denormalizers and needed the denormalizer to sync events on the server
  24. - Implement generic interface - Get read model - Get necessary data - Use event data to update read model
  25. Implement IPipelineHook Actions on events as denormalizers PostCommit  denormalize on the fly Select  when rehydrating List of guid to keep track of denormalized commits (idempotence)
  26. - Only used to retrieve aggregate id from tour code and date
  27. TourFinished  TourCompleted ProductRegistered  ProductEnlisted