ServiceStack provides a clean, message-based approach to building web services compared to traditional frameworks like ASP.NET Web API. ServiceStack uses simple POCO request and response classes to define service operations, avoiding tight coupling to frameworks or data formats. This makes services easier to version, test, and consume from different clients. ServiceStack also promotes a more loosely-coupled service architecture compared to traditional RPC-style services.
7. Implementation var appSettings = new AppSettings();
var config = appSettings.Get<Config>("my.config",
new Config { GitHubName = "mythz", TwitterName = "ServiceStack" });
Object graphs in Config deserialize into clean POCOs:
<appSettings name=”my.config” value=”{GitHubName:mythz,TwitterName:ServiceStack}”/> var github = new GithubGateway();
var repos = github.GetAllUserAndOrgsReposFor(config.GitHubName);
Avoid Web.Config completely with sensible default values
var twitter = new TwitterGateway();
var tweets = twitter.GetTimeline(config.TwitterName);
External providers to return clean POCOs "Loaded {0} repos and {1} tweets...".Print(repos.Count, tweets.Count);
OrmLiteConfig.DialectProvider = SqliteDialect.Provider;
//using (IDbConnection db = "~/db.sqlite".MapAbsolutePath().OpenDbConnection())
Use Extension methods to: using (IDbConnection db = ":memory:".OpenDbConnection())
{
db.DropAndCreateTable<Tweet>();
• DRY logic db.DropAndCreateTable<GithubRepo>();
• Create readable code
• Avoid abstractions / restrictions "nInserting {0} Tweets into Sqlite:".Print(tweets.Count);
db.InsertAll(tweets);
• Allow extensibility "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);
Code-First POCOs db.InsertAll(repos);
"nLatest {0} Repos from Sqlite:".Print(Show);
• Master authority of your models db.Select<GithubRepo>(q => q.OrderByDescending(x => x.Id).Limit(Show)).PrintDump();
• Start from C# project out }
• Schema’s inferred from POCO’s using (var redis = new RedisClient())
• Reduce industrial knowledge {
• Use Conventions where possible "nInserting {0} Tweets into Redis:".Print(tweets.Count);
redis.StoreAll(tweets);
• Typed API "n{0} Tweets from Redis:".Print(Show);
• Intelli-sense & productivity redis.GetAll<Tweet>().Take(Show).PrintDump();
• Refactoring "nInserting {0} Repos into Redis:".Print(repos.Count);
• Correctness redis.StoreAll(repos);
"n{0} Repos from Redis:".Print(Show);
redis.GetAll<GithubRepo>().Take(Show).PrintDump();
}
8. Benchmarks
https://github.com/mythz/ScalingDotNET
Brad Abrams (Google employee, former Microsoft PM and co-author of .NET Framework Design Guidelines)
12. @ServiceStack favorites:
Popular
114 Contributors
Since July 2011
https://github.com/ServiceStack/ServiceStack/wiki/Contributors http://www.servicestack.net/100k-downloads/
14. Where it began?
Followed the .NET Enterprise Play Book
• 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
15. What happened? Disaster
Code-gen DTO's were:
• 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
We pioneered:
• 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
16. 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
17. 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
19. 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
21. What is a Service?
It's to provide a service
to encapsulate some re-usable capabilities and make them available remotely
22. 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
Legend:
★ Goals that ServiceStack is optimized for :)
23. mflow
Less 1/2 development team
Magnitude more results
Best-Practices SOA Platform
Faster, more scalable services
ServiceStack was born
24. Visualizing ServiceStack
Built on raw ASP.NET IHttpHandler’s
Stand-alone HttpListener, MQ, RCON hosts
Runs Everywhere
Clean Slate using Code-First POCO’s
New Auth, Caching, Session Providers
27. 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
28. Message-based remote services are everywhere
Concurrency in F# – Part III – Erlang Style Message Passing
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
29. Message-based remote services are everywhere
Google Protocol Buffer Messages http://code.google.com/p/protobuf/
message Person {
required int32 id = 1;
required string name = 2;
optional string email = 3;
}
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
30. Message-based remote services are everywhere
Scala Actors Actors in Go
type ReadReq struct {
import scala.actors.Actor._
key string
import java.net.{InetAddress, UnknownHostException}
ack chan<- string
}
case class LookupIP(name: String, respondTo: Actor)
type WriteReq struct {
case class LookupResult(
key, val string
name: String,
}
address: Option[InetAddress]
)
c := make(chan interface{})
object NameResolver extends Actor {
go func() {
def act() {
m := make(map[string]string)
loop {
for {
react {
switch r := (<-c).(type) {
case LookupIP(name, actor) =>
case ReadReq:
actor ! LookupResult(name, getIp(name))
r.ack <- m[r.key]
}
case WriteReq:
}
m[r.key] = r.val
}
}
}
}
}()
http://www.cs.helsinki.fi/u/wikla/OTS/Sisalto/examples/html/ch30.html https://docs.google.com/present/view?id=df36r82q_5gdq5gzth
32. non-message-based service examples
namespace HelloWebAPI.Controllers
Your First ASP.NET Web API (C#) Tutorial: {
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));
}
}
}
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
33. Your First ASP.NET Web API (C#) Tutorial: The Same Services in ServiceStack:
public class ProductsControllerV2 : ApiController public class FindProducts : IReturn<List<Product>>
{ {
public IEnumerable<Product> GetAllProducts() public string Category { get; set; }
{ public decimal? PriceGreaterThan { get; set; }
return products; }
}
public class GetProduct : IReturn<Product>
public Product GetProductById(int id) {
{ public int? Id { get; set; }
var product = products.FirstOrDefault((p) => p.Id == id); public string Name { get; set; }
if (product == null) }
{
throw new HttpResponseException(HttpStatusCode.NotFound); public class ProductsService : IService
} {
return product; public object Get(FindProducts request)
} {
var ret = products.AsQueryable();
public Product GetProductByName(string categoryName) if (request.Category != null)
{ ret = ret.Where(x => x.Category == request.Category);
var product = products.FirstOrDefault((p) => p.Name == categoryName); if (request.PriceGreaterThan.HasValue)
if (product == null) ret = ret.Where(x => x.Price > request.PriceGreaterThan.Value);
{ return ret;
throw new HttpResponseException(HttpStatusCode.NotFound); }
}
return product; public object Get(GetProduct request)
} {
var product = request.Id.HasValue
//Extending existing services by adding more RPC method calls ? products.FirstOrDefault(x => x.Id == request.Id.Value)
public IEnumerable<Product> GetProductsByCategory(string category) : products.FirstOrDefault(x => x.Name == request.Name);
{
return products.Where( if (product == null)
(p) => string.Equals(p.Category, category, throw new HttpError(HttpStatusCode.NotFound, "Product does not exist");
StringComparison.OrdinalIgnoreCase));
} return product;
}
public IEnumerable<Product> GetProductsByPriceGreaterThan(decimal price) }
{
return products.Where((p) => p.Price > price);
} //Design of new services based on calling semantics and Return type,
} //e.g. Find* filters results, whilst Get* fetches unique records:
34. Familiar?
WCF-Style Services vs ServiceStack Web Services
public class Customers {
public interface IService
int[] Ids;
{
string[] UserNames;
Customer GetCustomerById(int id);
string[] Emails;
Customer[] GetCustomerByIds(int[] id);
}
Customer GetCustomerByUserName(string userName);
Customer[] GetCustomerByUserNames(string[] userNames);
public class CustomersResponse {
Customer GetCustomerByEmail(string email);
Customer[] Results;
Customer[] GetCustomerByEmails(string[] emails);
}
}
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.
35. Calling the Product Services in ServiceStack
private const string BaseUri = "http://localhost:1337"; /* All Products:
IRestClient client = new JsonServiceClient(BaseUri); [
{
"nAll Products:".Print(); Id: 1,
client.Get(new FindProducts()).PrintDump(); Name: Tomato Soup,
Category: Groceries,
Price: 1
},
{
Id: 2,
Name: Yo-yo,
Category: Toys,
// Notes: Price: 3.75
// - 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 Id: 3,
// - Service returns IEnumerable[Product] but JSON, JSV serializers happily handles List[Product] fine Name: Hammer,
Category: Hardware,
Price: 16.99
}
]
*/
/* Toy Products:
List<Product> toyProducts = client.Get(new FindProducts { Category = "Toys" }); [
"nToy Products:".Print(); {
toyProducts.PrintDump(); Id: 2,
Name: Yo-yo,
Category: Toys,
Price: 3.75
}
]
*/
36. Calling the Product Services in ServiceStack
List<Product> productsOver2Bucks = client.Get(new FindProducts { PriceGreaterThan = 2 }); /* Products over $2:
"nProducts over $2:".Print(); [
productsOver2Bucks.PrintDump(); {
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"}); /* Hardware over $2:
"nHardware over $2:".Print(); [
hardwareOver2Bucks.PrintDump(); {
Id: 3,
Name: Hammer,
Category: Hardware,
Price: 16.99
}
]
*/
Product product1 = client.Get(new GetProduct { Id = 1 }); /* Product with Id = 1:
"nProduct with Id = 1:".Print(); {
product1.PrintDump(); Id: 1,
Name: Tomato Soup,
Category: Groceries,
Price: 1
}
*/
37. Testable
New API Tests
protected static IRestClient[] RestClients = [Test, TestCaseSource("RestClients")]
{ public void Can_GET_AllReqstars(IRestClient client)
new JsonServiceClient(BaseUri), {
new XmlServiceClient(BaseUri), var allReqstars = client.Get<List<Reqstar>>("/reqstars");
new JsvServiceClient(BaseUri), Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
new MsgPackServiceClient(BaseUri), }
} ;
[Test, TestCaseSource("ServiceClients")]
protected static IServiceClient[] ServiceClients = public void Can_SEND_AllReqstars_PrettyTypedApi(IServiceClient client)
RestClients.OfType<IServiceClient>().ToArray(); {
var allReqstars = client.Send(new AllReqstars());
Assert.That(allReqstars.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
}
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
40. Hello World Implementation
[Route("/hellohtml/{Name}")] public class HelloService : Service {
public class HelloHtml public object Get(HelloHtml request) {
{ return "<h1>Hello, {0}!</h1>".Fmt(request.Name);
public string Name { get; set; } }
} [AddHeader(ContentType = "text/plain")]
public object Get(HelloText request) {
[Route("/hellotext/{Name}")] return "<h1>Hello, {0}!</h1>".Fmt(request.Name);
public class HelloText }
{ [AddHeader(ContentType = "image/png")]
public string Name { get; set; } public object Get(HelloImage request) {
} var width = request.Width.GetValueOrDefault(640);
var height = request.Height.GetValueOrDefault(360);
[Route("/helloimage/{Name}")] var bgColor = request.Background != null ? Color.FromName(request.Background) : Color.ForestGreen;
public class HelloImage var fgColor = request.Foreground != null ? Color.FromName(request.Foreground) : Color.White;
{ var image = new Bitmap(width, height);
public string Name { get; set; } using (var g = Graphics.FromImage(image)) {
g.Clear(bgColor);
public int? Width { get; set; } var drawString = "Hello, {0}!".Fmt(request.Name);
public int? Height { get; set; } var drawFont = new Font("Times", request.FontSize.GetValueOrDefault(40));
public int? FontSize { get; set; } var drawBrush = new SolidBrush(fgColor);
public string Foreground { get; set; } var drawRect = new RectangleF(0, 0, width, height);
public string Background { get; set; } var drawFormat = new StringFormat {
} LineAlignment = StringAlignment.Center,
Alignment = StringAlignment.Center };
[Route("/hello/{Name}")] g.DrawString(drawString, drawFont, drawBrush, drawRect, drawFormat);
public class Hello : IReturn<HelloResponse> var ms = new MemoryStream();
{ image.Save(ms, ImageFormat.Png);
public string Name { get; set; } return ms;
} }
}
public class HelloResponse public object Get(Hello request) {
{ return new HelloResponse { Result = "Hello, {0}!".Fmt(request.Name) };
public string Result { get; set; } }
} }
C# Client calls with: //var response = client.Get(new Hello { Name = "ServiceStack" });
53. https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
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/
54. https://github.com/ServiceStack/ServiceStack/wiki/Validation
Features: Validation
End-to-end typed API for exceptions
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
55. https://github.com/ServiceStack/ServiceStack/wiki/Caching
Features: Caching Providers
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
http://www.servicestack.net/ServiceStack.Northwind/