22
Lessons from the Trenches Dan Bishop RavenHQ [email protected] github.com/bishbulb twitter.com/bishbulb

Lessons from the Trenches - Building Enterprise Applications with RavenDB

Embed Size (px)

Citation preview

Page 1: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Lessons from the Trenches

Dan BishopRavenHQ

[email protected]/bishbulbtwitter.com/bishbulb

Page 2: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Typical Project Lifecycle

Technical Discovery

Convince Stakeholde

rsPrototype Iterate Launch

MVP Iterate

Life is good! Life is good, right?

Page 3: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Reality Sets In• Business is not “finished” with the MVP solution• Users are never satisfied• User hacks are conceived and proliferated• Small gaps grow into chasms• Production support issues start coming in• Things aren’t shiny anymore

Page 4: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Data Conversions• Properties are added and removed• Data types change• Class names and namespaces change• Documents are split or merged• Indexes change

Page 5: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Data Conversions – Adding Properties

public class OrderDelivery {public string Id { get; set; } public string Address { get; set; }public DateTime DeliveryDate { get;

set; }}

{"Address": "100 First St, NY, NY",

"DeliveryDate": "2016-05-30T12:30:00"}

public class OrderDelivery {public string Id { get; set; } public string Address { get; set; }public DateTime DeliveryDate { get;

set; }public bool Expedited { get; set; }

}

Page 6: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Data Conversions – Adding Propertiespublic class OrderDeliveriesIndex : AbstractIndexCreationTask<OrderDelivery>{ public OrderDeliveriesIndex() { Map = docs => from doc in docs select new { doc.Address, doc.DeliveryDate, doc.Expedited }; }}

Page 7: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Data Conversions – Adding Properties

var expeditedDeliveries = session .Query<OrderDelivery, OrderDeliveriesIndex>() .Where(x => x.Expedited);

if (expeditedDeliveries.Count > 10) { // email delivery manager}

Page 8: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Javascript Patchingvar result = documentStore.DatabaseCommands.UpdateByIndex("Raven/DocumentsByEntityName", new IndexQuery { Query = "Tag: OrderDeliveries" }, new ScriptedPatchRequest { Script = "this.Expedited = true;" }, new BulkOperationOptions { AllowStale = false });result.WaitForCompletion();

Page 9: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Javascript Patchingvar result = documentStore.DatabaseCommands.UpdateByIndex("Raven/DocumentsByEntityName", new IndexQuery { Query = "Tag: OrderDeliveries" }, new ScriptedPatchRequest { Script = @" var customerLookup = JSON.parse('customers'); this.Expedited = customerLookup[this.CustomerId].IsPreferred;", Values = new Dictionary<string, object> { { "customers", JsonConvert.SerializeObject(customers) } } });result.WaitForCompletion();

Page 10: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Javascript Patching• Patch scripts always run against an index• Make patch scripts idempotent• Consider rollback strategy• Split changes into multiple releases

• Debugging• Test in the Studio, use output() function• View patch progress in RavenDB 3.5

Page 11: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Data Conversions – Removing Properties

• Safe by default• Properties in the underlying document are retained

documentStore.Conventions.PreserveDocumentPropertiesNotFoundOnModel = true;

• Clutter by default• Keep track of the properties being removed• Run Javascript patch scripts in the next release to

remove the properties

Page 12: Lessons from the Trenches - Building Enterprise Applications with RavenDB

You Want All the Data?• Raven intentionally makes this hard with the

normal APIs• Indexes can be stale, query results limited to

1024 results• Streaming to the rescue

var results = session.Advanced.Stream<OrderDelivery>("orderdeliveries-");

session.Advanced.LoadStartingWith<OrderDelivery>("orderdeliveries-", start: 0, pageSize: 1000);

var query = session.Query<OrderDelivery, OrderDeliveriesIndex>() .Where(x => !x.Delivered);var results = session.Advanced.Stream(query);

• Other tips

session.Advanced.MaxNumberOfRequestsPerSession = int.MaxValue;

Page 13: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Semantic Document Identifiers• Identifiers that have embedded data/context• employees/1/deliveryroutes/2016-06-01

• Prevents logical duplicates• Enables multi-load with one round-trip• employees/1/preferences• employees/1/workschedule

•deliveryroutes/2016-06-01/employees/1• Enables you to bypass indexes altogether• Example: Get all delivery routes for a particular

day

Page 14: Lessons from the Trenches - Building Enterprise Applications with RavenDB

LoadDocumentManager

Employee1

Employee2

EmployeeN

public EmployeesIndex(){ Map = employeees => from employee in employeees let manager = LoadDocument<Manager>(employee.ManagerId) select new { employee.Id, ManagerName = manager.Name };}

Page 15: Lessons from the Trenches - Building Enterprise Applications with RavenDB

LoadDocumentManager

CompanyVehicle

public CompanyVehiclesIndex(){ Map = vehicles => from vehicle in vehicles let manager = LoadDocument<Manager>(vehicle.ManagerId) select new { vehicle.Id, ManagerName = manager.Name };}

Page 16: Lessons from the Trenches - Building Enterprise Applications with RavenDB

LoadDocument• One to many relationships could cause problems• Only when updating the document that has many

other documents referencing it• One to one relationships are fine• Look for alternatives• Transformers• Data duplication

Page 17: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Auto vs Static Indexes• Auto indexes are great for prototyping• Consider the implications<appSettings> <add key="Raven/CreateAutoIndexesForAdHocQueriesIfNeeded" value="false" /></appSettings>

• Indexes are resource intensive• Be aware of the indexes you have

Page 18: Lessons from the Trenches - Building Enterprise Applications with RavenDB

WaitForNonStaleResults• Gives you up-to-date results from an index

• Dealing with CRUD views• Make the writer wait for indexes, not the reader

var results = session .Query<OrderDelivery, OrderDeliveriesIndex>() .Customize(x => x.WaitForNonStaleResultsAsOfNow());

Page 19: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Consistency Issues• ACID provides specific guarantees• Distributed Transactions• DTC commit and rollback happen in a separate thread

from updates• Rather than locking the underlying documents, Raven will

allow you to access them but will return an HTTP 203 Non-Authoritative response

• Scenario: NServiceBus processing chain of messagesUpdateUserProfile ConfirmPhoneNumber

session.Advanced.AllowNonAuthoritativeInformation = false;

Page 20: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Replication Failover

Create Order

Upgrade to Expedited Delivery

Schedule Delivery Failover to secondary node

• Consider multi-transaction business workflows• What happens if the Raven client fails over to

another node midway through the processing sequence?

Page 21: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Replication Failover• Consider the risk/reward of allowing automatic

failover • Manual failover is an option• Secondary nodes are treated as “hot standby”

nodes• Mix-and-match failover behaviors• Consider SLAs and business needs when

deciding on failover strategies• These tradeoffs are not specific to RavenDB• All distributed systems have similar challenges

Page 22: Lessons from the Trenches - Building Enterprise Applications with RavenDB

Questions