Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What is the ServiceStack

What is the ServiceStack

What is it?
Where did it come from?
What does it do?

Avatar for Demis Bellot

Demis Bellot

October 22, 2012
Tweet

Other Decks in Technology

Transcript

  1. What is the ServiceStack Demis Bellot @demisbellot https://github.com/mythz https://github.com/ServiceStack http://www.servicestack.net

    http://www.servicestack.net/mythz_blog/ http://careers.stackoverflow.com/mythz Core Team Sergey Bogdanov @desunit Steffen Müller @arxisos
  2. What is the ServiceStack • What is it? • Where

    did it come from? • What does it do?
  3. More than Services Simple - Fast - Lightweight - Testable

    - Clean ServiceStack.Text ServiceStack.Redis ServiceStack.OrmLite ServiceStack.Caching .NET’s fastest JSON, JSV, CSV Text Serializers .NET’s leading Redis Client Fast, typed, Code-First Micro ORM SQL Server, Sqlite, PostgreSQL, MySQL, Oracle, Firebird Clean Caching Provider Interfaces In-Memory, Redis, Memcached, Azure, Disk More: node.js-powered Bundler, Logging, Auto-Mappers, Service Clients, Markdown Razor, Configuration Providers, Enhanced Funq IOC
  4. More than Services Simple - Fast - Lightweight - Testable

    - Clean ServiceStack.Text ServiceStack.Redis ServiceStack.OrmLite ServiceStack.Caching .NET’s fastest JSON, JSV, CSV Text Serilaizers .NET’s leading Redis Client Fast, typed, Code-First Micro ORM SQL Server, Sqlite, PostgreSQL, MySQL, Oracle, Firebird Clean Caching Provider Interfaces In-Memory, Redis, Memcached, Azure, Disk More: node.js-powered Bundler, Logging, Auto-Mappers, Service Clients, Markdown Razor, Configuration Providers, Enhanced Funq IOC Code-First POCOs
  5. var appSettings = new AppSettings(); var config = appSettings.Get<Config>("my.config", new

    Config { GitHubName = "mythz", TwitterName = "ServiceStack" }); var github = new GithubGateway(); var repos = github.GetAllUserAndOrgsReposFor(config.GitHubName); var twitter = new TwitterGateway(); var tweets = twitter.GetTimeline(config.TwitterName); "Loaded {0} repos and {1} tweets...".Print(repos.Count, tweets.Count); OrmLiteConfig.DialectProvider = SqliteDialect.Provider; //using (IDbConnection db = "~/db.sqlite".MapAbsolutePath().OpenDbConnection()) using (IDbConnection db = ":memory:".OpenDbConnection()) { db.DropAndCreateTable<Tweet>(); db.DropAndCreateTable<GithubRepo>(); "\nInserting {0} Tweets into Sqlite:".Print(tweets.Count); db.InsertAll(tweets); "\nLatest {0} Tweets from Sqlite:".Print(Show); db.Select<Tweet>(q => q.OrderByDescending(x => x.Id).Limit(Show)).PrintDump(); "\nInserting {0} Repos into Sqlite:".Print(repos.Count); db.InsertAll(repos); "\nLatest {0} Repos from Sqlite:".Print(Show); db.Select<GithubRepo>(q => q.OrderByDescending(x => x.Id).Limit(Show)).PrintDump(); } using (var redis = new RedisClient()) { "\nInserting {0} Tweets into Redis:".Print(tweets.Count); redis.StoreAll(tweets); "\n{0} Tweets from Redis:".Print(Show); redis.GetAll<Tweet>().Take(Show).PrintDump(); "\nInserting {0} Repos into Redis:".Print(repos.Count); redis.StoreAll(repos); "\n{0} Repos from Redis:".Print(Show); redis.GetAll<GithubRepo>().Take(Show).PrintDump(); } Implementation <appSettings name=”my.config” value=”{GitHubName:mythz,TwitterName:ServiceStack}”/> Object graphs in Config deserialize into clean POCOs: Avoid Web.Config completely with sensible default values External providers to return clean POCOs Use Extension methods to: • DRY logic • Create readable code • Avoid abstractions / restrictions • Allow extensibility Code-First POCOs • Master authority of your models • Start from C# project out • Schema’s inferred from POCO’s • Reduce industrial knowledge • Use Conventions where possible • Typed API • Intelli-sense & productivity • Refactoring • Correctness
  6. Where it began? • Leveraged external Enterprise consultants • Adopted

    Microsoft's prescribed Enterprise SOA solution at the time: CSF, WCF and BizTalk • We had an Enterprise / Technical Architect round-table • Split Internal systems into different independent SOA services (we really wanted to do SOA :) • Enterprise architects defined web service contracts using UML and XSDs: • XSD Messages were implementation ignorant • Explicitly validated XSD conformance at runtime • Leveraged SOAP/XML/XSD: Headers, Inheritance, Namespaces • UML -> XSD's -> DTOs -> used in WCF Service Contracts -> WSDLs -> Service Reference Proxies • CSF's state-full session was used as a Bus orchestrating communication between services • Services were discoverable, participating in CSF's Session • Service endpoints were WCF Services, leveraged SOAP headers for extra WS-* compliance • Wrote code Designed perfectly normalized databases Followed the .NET Enterprise Play Book
  7. What happened? • Brittle: A simple namespace change would break

    existing messages • Infectious: Domain logic binded to code-gen DTOs and version changes led to multiple parallel implementations • Friction-encumbered: Services needed to be built/ deployed in lock-step with each other Disaster Code-gen DTO's were: • Xml everywhere: We had XML config to manage our XML- configured services and resources • Week-long downtimes: between breaking major upgrades, required to flush old messages out of the system • Glacial Development: The turn-around time to get a field from UML/XSD/dll to C# was measured in days • Enterprise Hacks: Added "ViewBags" to most messages to allow us to stuff arbitrary data in existing messages without changing the schema We pioneered:
  8. What happened? • SOAP • Frail, Slow, Poor programmatic fit,

    Only accessible to SOAP Clients, Incompatible impls • RPC method signatures • Brittle, promotes lots of chatty client-specific API, Typed-API mandates code-gen, blurred service layer • Code-Gen • Changes breaks code, inhibits DRY, forces abstraction, multiple versions results in parallel implementations • Bus / State-full Sessions • Less accessible, harder to debug and test, easier to fail
  9. What we did right? • SOA Design: Coarse-grained, well-defined, self-describing,

    time-decoupled, re-usable messages • Pub/Sub: Promotes the most loosely coupled architecture i.e. Sender decoupled from Receiver • Message Queues: Replay-able async idempotent messages provide the most reliable and durable inter-service communications • Martin Fowler & Co knew what the best-practices for remote services were: • Remote Facade: Using message-based, coarse-grained/batch-full interfaces minimizing round-trips, promoting creation of fewer but tolerant and version-able service interfaces • DTOs: Avoid any effort expended dealing with wire-format in application code • Gateway: Removes comms logic in app code and provides a swappable comms layer with a good isolation and testing abstraction
  10. What is a Service? What it’s not It's NOT to

    force the use of a particular technology or platform It's NOT to conform to a pre-defined specification or constraints It's NOT to use a pre-determined format or follow a pre-scribed architecture
  11. What is a Service? It's to provide a service to

    encapsulate some re-usable capabilities and make them available remotely
  12. What is a Service? It's to provide a service to

    encapsulate some re-usable capabilities and make them available remotely ★ To as many clients as possible ★ In the most accessible and interoperable way ★ With the least amount of effort ★ With maximum amount of re-use Good characteristics ★ Version-able, tolerant and resilient to changes ★ End-to-end productivity ★ Goals that ServiceStack is optimized for :) Legend:
  13. mflow Less 1/2 development team Magnitude more results Best-Practices SOA

    Platform Faster, more scalable services ServiceStack was born
  14. Visualizing ServiceStack Built on raw ASP.NET IHttpHandler’s Runs Everywhere Clean

    Slate using Code-First POCO’s New Auth, Caching, Session Providers Stand-alone HttpListener, MQ, RCON hosts
  15. Advantages of Message-based Services • They're easy to version and

    evolve as you're freely able to add/remove functionality without error • They're easy to route, chain, hijack and decorate through to different handlers and pipelines • They're easy to serialize and proxy through to remote services • They're easy to log, record, defer and replay • They're easy to map and translate to and from domain models • Ideal for concurrency as immutable messages are thread-safe and are easily multiplexed • They're easy to extend with generic solutions and re-usable functionality around https://github.com/ServiceStack/ServiceStack/wiki/Advantages-of-message-based-web-services
  16. Message-based remote services are everywhere type Message = Word of

    string | Fetch of IChannel<Map<string,int>> | Stop type WordCountingAgent() = let counter = MailboxProcessor.Start(fun inbox -> let rec loop(words : Map<string,int>) = async { let! msg = inbox.Receive() match msg with | Word word -> if words.ContainsKey word then let count = words.[word] let words = words.Remove word return! loop(words.Add (word, (count + 1)) ) else return! loop(words.Add (word, 1) ) | Stop -> return () | Fetch replyChannel -> do replyChannel.Post(words) //post to reply channel and continue return! loop(words) } loop(Map.empty)) //The initial state member a.AddWord(n) = counter.Post(Word(n)) member a.Stop() = counter.Post(Stop) member a.Fetch() = counter.PostSync(fun replyChannel -> Fetch(replyChannel)) http://strangelights.com/blog/archive/2007/10/24/1601.aspx Concurrency in F# – Part III – Erlang Style Message Passing
  17. Message-based remote services are everywhere Google Protocol Buffer Messages Amazon

    C# SDK Petboard: An ASP.NET Sample Using Amazon S3 and Amazon SimpleDB SelectRequest selectRequest = new SelectRequest() .WithSelectExpression("select * from `petboard-public`"); SelectResponse selectResponse = _simpleDBClient.Select(selectRequest); http://aws.amazon.com/articles/3592 message Person { required int32 id = 1; required string name = 2; optional string email = 3; } http://code.google.com/p/protobuf/
  18. Message-based remote services are everywhere Scala Actors https://docs.google.com/present/view?id=df36r82q_5gdq5gzth http://www.cs.helsinki.fi/u/wikla/OTS/Sisalto/examples/html/ch30.html import

    scala.actors.Actor._ import java.net.{InetAddress, UnknownHostException} case class LookupIP(name: String, respondTo: Actor) case class LookupResult( name: String, address: Option[InetAddress] ) object NameResolver extends Actor { def act() { loop { react { case LookupIP(name, actor) => actor ! LookupResult(name, getIp(name)) } } } } type ReadReq struct { key string ack chan<- string } type WriteReq struct { key, val string } c := make(chan interface{}) go func() { m := make(map[string]string) for { switch r := (<-c).(type) { case ReadReq: r.ack <- m[r.key] case WriteReq: m[r.key] = r.val } } }() Actors in Go
  19. non-message-based service examples namespace HelloWebAPI.Controllers { public class ProductsController :

    ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public Product GetProductById(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public IEnumerable<Product> GetProductsByCategory(string category) { return products.Where( (p) => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } } } Your First ASP.NET Web API (C#) Tutorial: http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
  20. Your First ASP.NET Web API (C#) Tutorial: public class ProductsControllerV2

    : ApiController { public IEnumerable<Product> GetAllProducts() { return products; } public Product GetProductById(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public Product GetProductByName(string categoryName) { var product = products.FirstOrDefault((p) => p.Name == categoryName); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } //Extending existing services by adding more RPC method calls public IEnumerable<Product> GetProductsByCategory(string category) { return products.Where( (p) => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } public IEnumerable<Product> GetProductsByPriceGreaterThan(decimal price) { return products.Where((p) => p.Price > price); } } public class FindProducts : IReturn<List<Product>> { public string Category { get; set; } public decimal? PriceGreaterThan { get; set; } } public class GetProduct : IReturn<Product> { public int? Id { get; set; } public string Name { get; set; } } public class ProductsService : IService { public object Get(FindProducts request) { var ret = products.AsQueryable(); if (request.Category != null) ret = ret.Where(x => x.Category == request.Category); if (request.PriceGreaterThan.HasValue) ret = ret.Where(x => x.Price > request.PriceGreaterThan.Value); return ret; } public object Get(GetProduct request) { var product = request.Id.HasValue ? products.FirstOrDefault(x => x.Id == request.Id.Value) : products.FirstOrDefault(x => x.Name == request.Name); if (product == null) throw new HttpError(HttpStatusCode.NotFound, "Product does not exist"); return product; } } //Design of new services based on calling semantics and Return type, //e.g. Find* filters results, whilst Get* fetches unique records: The Same Services in ServiceStack:
  21. Familiar? WCF-Style Services ServiceStack Web Services vs public interface IService

    { Customer GetCustomerById(int id); Customer[] GetCustomerByIds(int[] id); Customer GetCustomerByUserName(string userName); Customer[] GetCustomerByUserNames(string[] userNames); Customer GetCustomerByEmail(string email); Customer[] GetCustomerByEmails(string[] emails); } public class Customers { int[] Ids; string[] UserNames; string[] Emails; } public class CustomersResponse { Customer[] Results; } Any combination of the above can be fulfilled by 1 remote call, by the same single web service - i.e what ServiceStack encourages Fewer and more batch-full services require less maintenance and promote the development of more re-usable and efficient services. In addition, message APIs are much more resilient to changes as you're able to safely add more functionality or return more data without breaking or needing to re-gen existing clients. Message-based APIs also lend them better for cached, asynchronous, deferred, proxied and reliable execution with the use of brokers and proxies.
  22. Calling the Product Services in ServiceStack private const string BaseUri

    = "http://localhost:1337"; IRestClient client = new JsonServiceClient(BaseUri); "\nAll Products:".Print(); client.Get(new FindProducts()).PrintDump(); /* All Products: [ { Id: 1, Name: Tomato Soup, Category: Groceries, Price: 1 }, { Id: 2, Name: Yo-yo, Category: Toys, Price: 3.75 }, { Id: 3, Name: Hammer, Category: Hardware, Price: 16.99 } ] */ List<Product> toyProducts = client.Get(new FindProducts { Category = "Toys" }); "\nToy Products:".Print(); toyProducts.PrintDump(); /* Toy Products: [ { Id: 2, Name: Yo-yo, Category: Toys, Price: 3.75 } ] */ // Notes: // - No Code-Gen: Same generic service client used for all operations // - When Custom Routes are specified it uses those, otherwise falls back to pre-defined routes // - IRestClient interface implemented by JSON, JSV, Xml, MessagePack, ProtoBuf Service Clients // - Service returns IEnumerable[Product] but JSON, JSV serializers happily handles List[Product] fine
  23. Calling the Product Services in ServiceStack List<Product> productsOver2Bucks = client.Get(new

    FindProducts { PriceGreaterThan = 2 }); "\nProducts over $2:".Print(); productsOver2Bucks.PrintDump(); /* Products over $2: [ { Id: 2, Name: Yo-yo, Category: Toys, Price: 3.75 }, { Id: 3, Name: Hammer, Category: Hardware, Price: 16.99 } ] */ var hardwareOver2Bucks = client.Get(new FindProducts {PriceGreaterThan=2, Category="Hardware"}); "\nHardware over $2:".Print(); hardwareOver2Bucks.PrintDump(); /* Hardware over $2: [ { Id: 3, Name: Hammer, Category: Hardware, Price: 16.99 } ] */ Product product1 = client.Get(new GetProduct { Id = 1 }); "\nProduct with Id = 1:".Print(); product1.PrintDump(); /* Product with Id = 1: { Id: 1, Name: Tomato Soup, Category: Groceries, Price: 1 } */
  24. Testable protected static IRestClient[] RestClients = { new JsonServiceClient(BaseUri), new

    XmlServiceClient(BaseUri), new JsvServiceClient(BaseUri), new MsgPackServiceClient(BaseUri), } ; protected static IServiceClient[] ServiceClients = RestClients.OfType<IServiceClient>().ToArray(); [Test, TestCaseSource("RestClients")] public void Can_GET_AllReqstars(IRestClient client) { var allReqstars = client.Get<List<Reqstar>>("/reqstars"); Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length)); } [Test, TestCaseSource("ServiceClients")] public void Can_SEND_AllReqstars_PrettyTypedApi(IServiceClient client) { var allReqstars = client.Send(new AllReqstars()); Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length)); } New API Tests https://github.com/ServiceStack/ServiceStack/blob/master/tests/RazorRockstars.Console.Files/ReqStarsService.cs Same Unit Test can be re-used in JSON, XML, JSV, SOAP 1.1/1.2 Integration Tests https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Tests/WebServicesTests.cs
  25. Hello World Implementation [Route("/hellotext/{Name}")] public class HelloText { public string

    Name { get; set; } } [Route("/hellohtml/{Name}")] public class HelloHtml { public string Name { get; set; } } [Route("/helloimage/{Name}")] public class HelloImage { public string Name { get; set; } public int? Width { get; set; } public int? Height { get; set; } public int? FontSize { get; set; } public string Foreground { get; set; } public string Background { get; set; } } [Route("/hello/{Name}")] public class Hello : IReturn<HelloResponse> { public string Name { get; set; } } public class HelloResponse { public string Result { get; set; } } public class HelloService : Service { public object Get(HelloHtml request) { return "<h1>Hello, {0}!</h1>".Fmt(request.Name); } [AddHeader(ContentType = "text/plain")] public object Get(HelloText request) { return "<h1>Hello, {0}!</h1>".Fmt(request.Name); } [AddHeader(ContentType = "image/png")] public object Get(HelloImage request) { var width = request.Width.GetValueOrDefault(640); var height = request.Height.GetValueOrDefault(360); var bgColor = request.Background != null ? Color.FromName(request.Background) : Color.ForestGreen; var fgColor = request.Foreground != null ? Color.FromName(request.Foreground) : Color.White; var image = new Bitmap(width, height); using (var g = Graphics.FromImage(image)) { g.Clear(bgColor); var drawString = "Hello, {0}!".Fmt(request.Name); var drawFont = new Font("Times", request.FontSize.GetValueOrDefault(40)); var drawBrush = new SolidBrush(fgColor); var drawRect = new RectangleF(0, 0, width, height); var drawFormat = new StringFormat { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Center }; g.DrawString(drawString, drawFont, drawBrush, drawRect, drawFormat); var ms = new MemoryStream(); image.Save(ms, ImageFormat.Png); return ms; } } public object Get(Hello request) { return new HelloResponse { Result = "Hello, {0}!".Fmt(request.Name) }; } } C# Client calls with: //var response = client.Get(new Hello { Name = "ServiceStack" });
  26. SMessage Implementation public partial class SMessageService : Service { public

    EmailProvider Email { get; set; } public FacebookGateway Facebook { get; set; } public TwitterGateway Twitter { get; set; } public IMessageFactory MsgFactory { get; set; } public object Any(SMessage request) { var sw = Stopwatch.StartNew(); if (!request.Defer) { var results = new List<SMessageReceipt>(); results.AddRange(Email.Send(request)); results.AddRange(Facebook.Send(request)); results.AddRange(Twitter.Send(request)); Db.InsertAll(results); } else { using (var producer = MsgFactory.CreateMessageProducer()) { Email.CreateMessages(request).ForEach(producer.Publish); Facebook.CreateMessages(request).ForEach(producer.Publish); Twitter.CreateMessages(request).ForEach(producer.Publish); } } return new SMessageResponse { TimeTakenMs = sw.ElapsedMilliseconds, }; } } public partial class SMessageService : Service { public object Any(EmailMessage request) { var sw = Stopwatch.StartNew(); Db.InsertAll(Email.Process(request)); return new SMessageResponse { TimeTakenMs = sw.ElapsedMilliseconds }; } public object Any(CallFacebook request) { var sw = Stopwatch.StartNew(); Db.InsertAll(Facebook.Process(request)); return new SMessageResponse { TimeTakenMs = sw.ElapsedMilliseconds }; } public object Any(PostStatusTwitter request) { var sw = Stopwatch.StartNew(); Db.InsertAll(Twitter.Process(request)); return new SMessageResponse { TimeTakenMs = sw.ElapsedMilliseconds }; } public object Any(FindReceipts request) { var skip = request.Skip.GetValueOrDefault(0); var take = request.Take.GetValueOrDefault(20); return Db.Select<SMessageReceipt>(q => q.Limit(skip, take)); } }
  27. Why Mono? Scale at $0 licensing cost • Redis •

    PostgreSQL, MySQL, SQLite, Firebird Lets build the next Instagram!
  28. Why Mono? Scale at $0 licensing cost 2.67 GHz 2GB

    RAM 80GB HDD 4TB traffic Where to host servicestack.net? 1.67 GHz 1.75GB RAM 100GB HDD 2TB traffic
  29. Why Mono? Scale at $0 licensing cost 2.67 GHz 2GB

    RAM 80GB HDD 4TB traffic Where to host servicestack.net? 1.67 GHz 1.75GB RAM 100GB HDD 2TB traffic €219.50 mo €2,634 year €19.90 mo €238.8 year > 11x Cheaper!
  30. Features: Authentication Providers       var  appSettings  =  new

     AppSettings();       Plugins.Add(new  AuthFeature(         ()  =>  new  CustomUserSession(),                                      //Use  your  own  typed  Custom  UserSession  type         new  IAuthProvider[]  {                                        new  CredentialsAuthProvider(),                              //HTML  Form  post  of  UserName/Password  credentials                                        new  TwitterAuthProvider(appSettings),                //Sign-­‐in  with  Twitter                                        new  FacebookAuthProvider(appSettings),              //Sign-­‐in  with  Facebook                                        new  DigestAuthProvider(appSettings),                  //Sign-­‐in  with  Digest  Auth                                        new  BasicAuthProvider(),                                          //Sign-­‐in  with  Basic  Auth                                        new  GoogleOpenIdOAuthProvider(appSettings),    //Sign-­‐in  with  Google  OpenId                                        new  YahooOpenIdOAuthProvider(appSettings),      //Sign-­‐in  with  Yahoo  OpenId                                        new  OpenIdOAuthProvider(appSettings),                //Sign-­‐in  with  Custom  OpenId                                })); https://github.com/ServiceStack/SocialBootstrapApi/ https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
  31. Features: Validation try { var client = new JsonServiceClient(BaseUri); var

    response = client.Send<UserResponse>(new User()); } catch (WebServiceException webEx) { /* webEx.StatusCode = 400 webEx.ErrorCode = ArgumentNullException webEx.Message = Value cannot be null. Parameter name: Name webEx.StackTrace = (your Server Exception StackTrace - if DebugMode is enabled) webEx.ResponseDto = (your populated Response DTO) webEx.ResponseStatus = (your populated Response Status DTO) webEx.GetFieldErrors() = (individual errors for each field if any) */ } https://github.com/ServiceStack/ServiceStack/wiki/Validation End-to-end typed API for exceptions https://github.com/ServiceStack/ServiceStack/wiki/Validation
  32. Features: Caching Providers http://www.servicestack.net/ServiceStack.Northwind/ https://github.com/ServiceStack/ServiceStack/wiki/Caching container.Register<ICacheClient>(new MemoryCacheClient()); public class OrdersService

    : Service { public object Get(CachedOrders request) { var cacheKey = "unique_key_for_this_request"; return base.RequestContext.ToOptimizedResultUsingCache(base.Cache, cacheKey, () => { //Delegate is executed if item doesn't exist in cache //Any response DTO returned here will be cached automatically } ); } } Redis, Memcached, Disk and Azure Caching Providers also available Sessions works with any Cache Provider
  33. Features: Mini Profiler Built-in protected  void  Application_BeginRequest(object  src,  EventArgs  e)

    {        if  (Request.IsLocal)   Profiler.Start(); } protected  void  Application_EndRequest(object  src,  EventArgs  e) {        Profiler.Stop(); } https://github.com/ServiceStack/ServiceStack/wiki/Built-in-profiling