ServiceStack is an Opensource .NET and Mono REST Web Services framework. InfoQ had the opportunity to get some insights from Demis Bellot about the project. In Part 1 of this two-part interview, we discuss mainly about the motivation behind ServiceStack and various design choices made in the project.
InfoQ: What problems do you see in Microsoft's approach to Services and how does ServiceStack try to tackle it?
Demis: Some of Microsoft's problems are a result their general attitude to what they believe constitutes good framework design, others are just side-effects of trying to satisfy designer-first tooling and trying to provide a familiar API to designer-led developers:
-
Promotion of C# RPC method calls for service API designs leading to the design of chatty, client-specific APIs
-
Choosing to standardize around brittle, bloated, slow and over-complicated SOAP and WS-* serialization formats
-
Trying to achieve end-user simplicity with heavy artificial abstractions, UI designers and big tooling
-
Creating heavily abstracted APIs in an attempt to present developers an artificial server-side object model
-
Trying to unify all network endpoints under a shared abstracted object model
-
Overuse of XML Configuration and code-gen
-
Testability and Performance treated as after thoughts
RPC method signatures
The pursuit of providing developers a familiar RPC API with rich UI tooling in VS.NET (E.g. with the "Add Service Reference" dialog) trades initial simplicity at the expense of promoting remote service anti-patterns that leads to a path of un-necessary friction and brittleness in the long run. Unfortunately this same design is perpetuated in every web service framework Microsoft has produced, which have always promoted the creation of RPC Service API designs encouraging developers to treat remote services like local method calls. This is harmful in many ways, remote services are millions of times slower than method calls that by definition encapsulate an external dependency that is more susceptible to faults. Having your implicit service contract tied to server RPC method signatures also couples and blurs the implicit Service Contract of your API to its server implementation. Not enforcing a well-defined boundary is what encourages poor separation of concerns and the bad practices of returning Heavy ORM data models over the wire which because of their relational structure, cyclical relations are by design poor substitutes for DTOs that will sporadically fail. They also couple your implicit service contract to your underlying RDBMS data models imposing friction to change. Remote APIs are also disconnected from their server call sites which continue to evolve after client proxies are deployed, ideally frameworks should be promoting evolvable, resilient API designs to avoid runtime failures when services change.
Here's an illustration of the differences between the chatty RPC API that WCF promotes vs the Message-based API ServiceStack encourages. Whilst this example of the Web API tutorial re-written in ServiceStack shows how a message-based API encourages fewer, less chatty, more re-usable services.
By embracing Martin fowler's remote service best practices ServiceStack has been able to avoid many of these pitfalls that have historically plagued .NET web service developers:
Remote Facade
Encourages the use of message-based, coarse-grained/batch-full interfaces minimizing round-trips, promoting the creation of fewer but more tolerant and version-able service interfaces. ServiceStack's message-based design ensures developers always create coarse-grained API designs.
Data Transfer Object (MSDN)
Dictating the use of special-purpose POCOs (Plain Old CSharp Objects) to generate the wire format of your web services response. ServiceStack has always encouraged the use of DTOs that that are decoupled from its implementation and kept in its own dependency and implementation-free assembly. This strategy is what allows the same Service Types that are used to define the server with to be shared with all .NET clients providing an end-to-end typed API without the use of code-gen.
The Gateway (MSDN)
By encapsulating all server communications behind an explicit and re-usable client Gateway. This best-practice is violated with code-gen proxies (like WCF) which merges code-gen types with underlying service clients making it difficult to mock, test and substitute with different implementations. It's also common for changes to existing services to cause compilation errors in client code. This is rarely an issue with ServiceStack which is able to reuse generic service clients so the surface-area of change is limited to types, and because of our message-based design any functionality added or removed that isn't used wont affect existing clients.
ServiceStack adopts the Gateway pattern in its generic and re-usable typed .NET Service Clients. We also maintain Silverlight,JavaScript, Dart and even MQ Clients. Having all .NET Service Clients share the same interfaces makes it easy to test (intercept or log) as well as being able to easily switch between any of the available JSON, XML, JSV, MessagePack and ProtoBuf clients without changes to application code. This enables the optimal approach of developing with a debugger friendly text format like JSON then switch to use one of .NET's fastest binary formats Message Pack or ProtoBuf for min payload and max performance in deployment builds.
SOAP Web Services
SOAP is one of those technologies that should have never existed in its current form, or at least be marginalized to only areas where it provides a benefit. It's built on the false premise that in order to enable data interchange you need to opt-into complexity, to be as strict and explicit as possible forcing all implementations to implement a complex schema in order to communicate. In-fact the opposite has proven true, there is much less burden and complexity, the resulting output faster, more interoperable and versionable if a minimal and flexible format as found in JSON, CSV, Protocol Buffers, MessagePack, BSON, etc was used instead.
I always found it hard to imagine that despite being built on top of HTTP, the SOAP designers had the benefit of hindsight when they developed WSDL. I expect it would've been hard to justify how WSDL's introduction of Types, Messages, Operations, Ports, Bindings and Services was somehow a superior replacement to HTTP's simple URL identifier and Accept/Content-Type headers.
Despite it's name SOAP is neither Simple nor an Object Access Protocol and has many properties that make it a poor choice for developing services with, i.e:
-
It's bloated and slow - Routinely ending up on the wrong end of size and performance benchmarks
-
It's brittle - Simple namespace, field, enum or type changes can cause runtime exceptions
-
It's a poor programmatic fit - It doesn't map naturally to any object type system causing friction whenever projecting in and out of application code
-
It violates HTTP - HTTP includes simple Verbs for distributed access, e.g. GET's should be side-effect free, allowing it to be Cached by clients and middleware. Since every SOAP request is HTTP POST it is unable to take advantage of one of the primary benefits of HTTP
-
It has poor accessibility - A SOAP message is essentially an abstract container designed to transport custom XML payload bodies and SOAP headers. This is a poor approach as abstract message formats require more effort to access and serialize into whilst providing less typed access to your data. It also limits access to your services to only SOAP-Aware clients. As it provides no real-world value many Web APIs will no longer wrap responses in message containers and will simply serialize raw JSON and XML outputs as-is - a practice known as POX/J (Plain Old XML)
-
It's complex - SOAP, WSDLs, UDDI, WS-* introduces unnecessarily complexity when they're not needed. Eventually only contributors to the specs (or the SOAP framework developers) have a good understanding of the complete WS-* stack and how to best implement it with the few big iron frameworks that have attempted to adopt it.
-
Encourages code-gen - Given its complexity, SOAP is a generally unfeasible endeavour to implement without a SOAP framework and code-gen proxies
Given it goes against many of the core tenants of a service, it's surprising SOAP became as popular as it did. Having spent years developing services for Governments and Large Enterprises, the unfortunate reality is that for many of them exposing SOAP endpoints for their services is a mandatory requirement. However for Internet companies and value-focused Start-Ups it's a relatively non-existant technology, instead most have opted to create simple HTTP Apis returning Plain Old XML or JSON responses.
Despite being a poor option in most cases, ServiceStack continues to enable and support SOAP endpoints for your services as there still exists many enterprise systems that only allow connecting to SOAP endpoints. This is inline with ServiceStack's core objectives of providing maximum utility, accessibility and reach for your services.
SOAP is by design an extremely brittle and fragile format, where you'll typically see run-time errors occuring if a service deviates slightly from the WSDL upon which the client proxy was generated from. Although Failing fast is seen as an advantage in a static type system, it is an undesirable attribute for remote services where you want to achieve backwards and forwards compatibility so server API changes don't break existing clients. This goal is ignored by WCF which promotes RPC method signatures, the SOAP format and code-gen to provide what is likely the most fragile combination of technologies used in web service implementations today.
SOAP vs Protocol Buffers
It's interesting to see how SOAP compares with Google's solution for a simple IDL Protocol Buffers - which it uses for almost all of its internal RPC protocols and file formats:
-
It's multiple times smaller and and an order of magnitude faster than SOAP
-
Uses a simple DSL to define message-based APIs
-
.proto used to define Types that are a good programmatic fit for programming languages
-
Includes support for versioning
-
Provides native client and server bindings for Python, Java and C++
MessagePack RPC and the Facebook-developed Apache Thrift are other popular Open Source choices providing a fast, simple IDL with bindings for most popular platforms.
Despite being developed by different companies each of the above IDL's have ended up being much simpler, faster and easier to use than SOAP which based on the sheer size of the specification appears to have been developed by committees without any regard for performance, size or ease of implementation.
JSON
Despite its superior size and performance characteristics, Protocol Buffers and MessagePack both have the disadvantage of being binary formats. Since they're easier to create, maintain and debug, text-based formats and protocols generally end up being the more popular choice for the open web. The clear winner in the text-based data interchange formats in recent times has been JSON - a simple, compact self-describing text format that has the major advantage of being natively available in all javascript-enabled browsers by virtual of being instantly convertible to Javascript objects with browsers built-in "eval()" statement. Because of its popularity, modern browsers now include native support for JSON that provides a safer alternative to eval() with a fallback implementation in javascript available to older browsers. To learn more about JSON and its benefits, I recommend watching Douglas Crockford's entertaining talk Heresy & Heretical Open Source.
Because of its ubiquity, versatility and ease of implementation it's natively supported in most platforms and is quicky becoming the preferred data format for developing Web APIs - especially ones servicing Ajax clients.
In our own experiences we've found JSON to be a superior format for enabling services. It's a more compact, flexible, tolerant and resilient format that's easier to create, consume and debug, offering less friction and superior productivity over SOAP. The only problem with JSON at the time we started ServiceStack was that the Serializers in the .NET Framework were actually slower than their XML Serializers. Being performance conscience we weren't happy with accepting this trade-off so we released our own JSON serializer that ended up being more than 3x faster than any other .NET JSON Serializer and more than 2.5x faster than any serializer (inc. binary) in the .NET Framework.
Following the same minimal spirit of JSON, we've also developed the JSV Format which is like JSON but uses CSV-style escaping making it slightly faster, more compact and more human-friendly than JSON. It's useful for .NET to .NET services and it's also used for parsing Query Strings where it allows passing complex object graphs in GET requests in ServiceStack.
ServiceStack vs WCF Approach
Despite being very different service frameworks in implementation and approach, WCF and ServiceStack have fairly similar goals. ServiceStack takes a very service-orientated approach around building services where it's optimally designed to capture your service implementation in its most re-usable form. From this code-first, typed design we're able to infer greater intelli-gence of your services allowing us to generate XSD's, WSDLs, auto-generated metadata pages, expose pre-defined routes, all automatically. All functionality, features, endpoints and Content-Types added are built around your existing models and provide instant utility without additional effort and changes to application code. We refer to this approach as starting from code-first models as the master authority and projecting out.
WCF's goals are also to provide a services framework to enable services on multiple endpoints but they take a reverse angle, and provide a unified abstraction over all network endpoints which you have to configure your services to bind to. As a primary goal they're also one of the few implementations to have deep support of the WS-* stack.
Ultimately we both aim to provide an easy to use services framework, we just have very different ideas on how to achieve this simplicity.
Simplicity and Tackling Complexity
WCF seems to favour heavy abstraction, complex runtime XML configuration to manage it and big tooling to provide end-to-end connectivity for developers. This abstraction covers a lot of complex technology: its abstracted artificial server-side object model is complex, its configuration is complex, WSDLs are complex, and SOAP / WS-* are complex. The result of which requires numerous text books on each of the subjects to have a good understanding of it.
Abstractions
There is a school of thought that believes the way to tackle complexity is to add higher-level abstractions. The problem with this approach are that it only works when the abstraction is perfect (e.g a programming language over machine code). If its not, the moment you run into unexpected behavior or configuration, integration and interoperability issues you need to understand what's happening under all those layers. Adding more abstractions in these cases actually increases the conceptual space developers have to be familiar with and the Cognitive Overhead they have to know when developing against the higher-level abstraction. This makes it harder to reason about your code-base as you need to understand all the layers beneath it. To further compound the problem, WCF's server-side object model is new and artificial, new developers aren't familiar with it as there's nothing else like it and it doesn't teach you anything about the services or HTTP domain. So developers don't gain any transfer of knowledge when they move onto the next-best services framework. i.e. the investment in text books required to learn how to use WCF are better spent learning HTTP + TCP/IP as any knowledge gained remains useful when moving onto other web service frameworks, independent of language or platform.
At the technical implementation heavy abstractions incurs un-necessary performance overheads that are harder to optimize since they obstruct and impose friction when trying to interact with low-level APIs. Our approach is to only add abstractions when they're absolutely necessary, as seen with our thin IHttpRequest and IHttpResponse wrappers necessary to provide a common API for ASP.NET and self-hosted HttpLister hosts. Rather than adding layers of abstraction we prefer adding features orthogonally by wrapping DRY functionality around the low-level interfaces. This has the added benefit of allowing end-users freedoms to do the same thing and add their own enhancements in user-space libraries, promoting a lean, DRY and readable code-base. Exposing low-level APIs ensures we maintain a flexible framework where users have complete control over system outputs with access to multiple ways to easily customize the returned response so they're never limited and can control every byte that's returned.
WCF's Abstractions
WCF adopts a unique approach to services in that it tries to force all supported endpoints to standardize on a single artificial abstraction model. This approach suffers the side-effects from the general principles of abstractions where a unified abstraction model now takes into account the complexities of everything it's trying to abstract, whilst only being able to support the intersection of features and lowest common denominator functionality available in each endpoint. This results into an incomplete surface API, with the abstraction itself obstructing access to the underlying endpoints making each less accessible and configurable.
Being able to pull what WCF has accomplished is a huge technical feat in itself, requiring a massive investment in technical resources. But it's essentially a wasted effort since it results in a less productive and usable framework compared with standard approaches taken on alternative platforms. WCF also requires a lot more investment of effort from end-users in order to gain a cursory level of experience. For these reasons I don't expect we'd ever see a WCF-like framework or its unified endpoint abstraction undertaken by anyone else. Due to its complexity and sheer size of its technical implementation I doubt it will evolve and adapt to support new developer paradigms or service patterns either. I suspect WCF will suffer the same fate as WebForms where it will be relegated to a deprecated technology, losing favor to Microsofts next replacement framework.
We still plan on adding more endpoints to ServiceStack in addition to the REST, SOAP, MQ and RCON endpoints already available. But by taking a convention-based approach we avoid requiring heavy configuration, our message-based design avoids big-tooling and simplifies the surface-area needed to be implemented, starting from ideal C# and projecting out allows new functionality to be automatically available to end-users, without additional effort. The absence of a unified abstraction model and our de-coupled architecture allows us to add new endpoints and features orthogonally without imposing undue complexity on existing components.
All-in-all we're able to deliver a full-featured, better-performaning, easier to understand services framework in a much leaner and nimbler code-base, that requires drastically less techical effort in order to implement and maintain.
Heavy Configuration
XML Configuration is another idea embraced in WCF that we don't agree with, it inhibits testing and requires more effort to maintain. The only thing that should be in your applications config are parts of your application that are actually configurable. Code remains the superior way to define and compose your services dependencies and has the advantages of being debuggable and statically verifiable at compile-time. WCF requires a lot of it - it's easy to create complete applications in ServiceStack that's smaller than the size of the XML WCF Configuration required by a typical WCF Service. It ends up being simpler and cleaner if configuration happens at the IOC level - i.e. directly against the .NET Api of the features you want to enable.
Big Tooling
The problem with big tooling is that it breaks down whenever you try to do something new with it, or something it wasn't intended for. In these cases you'll become a slave to the tooling, limited in being able to provide functionality that it supports. Big tooling also doesn't survive framework rewrites as they encourage heavy and complex code-bases that are hard to evolve and re-factor. I anticipate it's this reason why Microsoft is forced into continually re-writing service frameworks instead of being able to evolve their existing ones, why WebApi isn't able to re-use MVC's existing abstractions or enable WCF's SOAP support despite adopting the same RPC API design and its self-hosting option already built on top of WCF.
Building on the lowest-level ASP.NET abstractions allows ServiceStack to provide great integration with ASP.NET MVC, where it's able to re-use the existing implementations for many components like Authentication filter attributes, Caching and Session providers easily in MVC. You can also easily call ServiceStack services from inside MVC with a little more than the cost of a C# method call.
ServiceStack's take on Complexity
ServiceStack by contrast is a much simpler picture, e.g. ServiceStack's Architecture fits on 1 page. It only requires 1 line of Web.config to enable just to tell ASP.NET to forward all requests to ServiceStack.
Conventions and Artficial Complexity
How best to tackle complexity is probably the biggest philosophical difference between ServiceStack vs Microsoft's approach to developing libraries and frameworks. Microsoft prefers to be explicit, configurable via xml, introduces new heavy artificial abstractions (forward-looking to support all potential use-cases), relies on abstractions to expose cosmetically simpler user-facing facades, optimizes for designer-friendly tooling and novice developers. Their primary motivation seems to be to present developers a familiar programming model and leverages big tooling to facilitate it. We see this with WebForms which provided WinForm developers an event-based programing model to develop websites and in WCF which lets developers create remote services using normal C# methods and interfaces. Their approaches generally requires significant technical effort to achieve resulting in large code-bases.
By contrast we view code-base size as code's worst enemy and avoid introducing Artificial complexity as a primary objective. i.e. we're vehemently opposed to introducing new concepts, abstractions or programming models. We instead prefer thin low-level interfaces mapping 1:1 with the underlying domain to maximize flexibility, reduce friction and minimize projection complexity when further customization is needed. DRY, high-level functionality is obtained with re-usable utils and extension methods. Adopting a code-first development model captures the essence of users intent in code enabling a more elegant non-compromized design, avoiding any projection complexity introduced when interfacing with designer tooling. All our libraries work with POCOs for maximum reusability and our built-in translators simplify effort in translating between domain-specific models.
We present users a best-practices message-based design, promoting the optimal approach for developing services from the start. Services are just normal C# classes, free of endpoint concerns and their dependencies auto-wired to drive users towards adopting good code-practices.
Conventions and reducing industrial knowledge are our best weapons to tackle complexity. Rather than being explicit it's better to provide a conventional default behavior that works as expected. This saves users from having to learn your frameworks APIs as expected standard behavior can be assumed and allows us to provide more functionality for free, like having all our built-in endpoints and formats automatically enabled out-of-the-box. You're also free to return any response from your services, which gets serialized into the Requested Content-Type.
Achieving simplicity
Ultimately we believe simplicity is best achieved by avoiding complexity in the first place, by:
-
Removing all moving parts and accumulated abstractions - Built on top of ASP.NET's raw IHttpHandlers
-
Use convention over configuration - All Services implementing IService are automatically wired-up and made available
-
Provide sensible defaults - ServiceStack just works out-of-the-box, no further configuration is needed
-
Enable all features - All in-built formats and end-points enabled by default, pre-defined conventional routes automatically available. Use config to opt-out
-
Automatic discovery - ServiceStack includes a /metadata page listing all services, the routes their available on, XSDs, WSDLs, etc
-
Introduce no new artificial constructs - ASP.NET developers will be instantly familiar with how to customize the HTTP Request and Response
-
Start with C# and Project out - Start with the ideal C# code and add orthoganal features so they're instantly available during normal development
-
Opinionated towards productivity - Every Request DTO in ServiceStack must be unique, this allows you call any service with just the Request DTO Name and body
-
Building functionality around POCOs - You can use the same POCO in ServiceStack as a DTO, OrmLite Data Model, Cache, Session, Config, etc
-
Using a Message-based design - Binding to a single model is much easier to implement and rationale about than a RPC method signature
-
Flexible - Provide a number of Custom Filters and Event Hooks to plug into and customize any stage of the Request and Response pipeline
Avoid big tooling and code-gen
We dislike code-gen as we believe it adds un-necessary friction to a project, we're also against the reliance on any big tooling in the core part of the development workflow.
Since the ServiceContract for your ServiceStack services is maintained in your Request and Response DTOs by design, we're able to provide a typed end-to-end API using nothing other than the types you used to build your services with and any of thegeneric and re-usable .NET Clients. This lets us provide the most succinct, typed, end-to-end API out of any service Framework in .NET.
Testability
Based on WCF's design and heavy reliance on Config, it doesn't appear that WCF was built with any testability in mind. It is however a core objective in ServiceStack, we provide an embedded (and overridable) IOC by default, encouraging good development practices from the start. Services need only implement an implementation-free IService marker interface or inheriting any of the convenient Service or ServiceBase classes - each of which are testable in isolation and completely mockable. The result of our efforts allows the same Unit Test to also serve as an XML, JSON, JSV, SOAP Integration Test. Our self-host HttpListeners makes it easy to perform In-Memory integration tests.
Performance
Performance is a primary objective in ServiceStack as we consider performance to be the most important feature. Our message-based design promotes fewer network calls and we take care to only expose user-facing APIs that are fast. Contributions are often rejected because they contain slow code. We're dilligent in our implementations, we don't use any runtime reflection or regular expressions preferring instead to use the faster alternative solutions.
Fastest Serialization Formats
We develop and maintain .NET's fastest JSON, JSV and CSV Text serializers and enable the 2 fastest binary serializers for .NETMessage Pack and Protocol Buffers via Plugins.
Rich Caching Providers
Since Caching is necessary in order to create high-performance services, we include a rich Caching Provider Model with implementations for In-Memory, Redis, Memcached, Azure and Amazon back-ends. The Caching APIs persist the most optimal format, e.g. if the client supports it, we'll store the compressed JSON output in the cache that is written directly to the Response stream on subsequent requests enabling the fastest response times possible in managed code.
Fixing .NET's performance problems
Whenever we identify bottlenecks in Microsoft's libraries, we'll routinely replace them with faster performing alternatives. We've done this with our Json Serializer, pluggable Gzip and Deflate Compression libraries, as well as avoiding the degrading performance issue plaguing ASP.NET developers by providing our own clean Session implementation that works with any of the above Caching providers.
Leading client for the fastest Distributed NoSQL DataStore
In our pursuit into technologies for developing high-performance services, we ended up developing and maintaining a .NET's leading C# Redis Client for the fastest distributed NoSQL data store - Redis.
About the Interviewee
Demis Bellot is a developer at Stack Exchange where he maintains StackOverflow Careers 2.0 Back Office Web & MQ services built on ServiceStack. He is the creator and project lead of ServiceStack.
Note: Part 2 of this interview will be published next week.