Service Virtualization With The Managed Services Engine
This article is based on prerelease versions of the Managed Services Engine and .NET Services. All information is subject to change.
This article discusses:
- Service virtualization basics
- Microsoft Services SOA Infrastructure
- Getting started with the Managed Services Engine
- Importing and configuring service resources
This article uses the following technologies:
Managed Services Engine, .NET Services, Windows Server, SQL Server, WCF
As businesses begin to adopt service-oriented architectures (SOA), they initially focus on service-enabling core business functions and processes. It's often accomplished by assigning ownership of individual services to autonomous domains within the business that largely operate in isolation from one another. This paradigm shift promotes loose-coupling, reuse, and the overall alignment of software assets with how the business actually works. When done right, the business ends up with a federated library of services that can be composed into solutions with greater agility. And as the overall service ecosystem grows over time, so do the potential benefits to the business.
While SOA can definitely provide benefits to the business, there's a common misperception that SOA makes things easier. This is simply not true. SOA embraces the fact that building distributed systems is inherently complex, and it outlines architectural principles that can help you manage some of that complexity. Large SOA initiatives are still inherently complex, but with a new set of challenges and issues. One of the greatest challenges in succeeding with SOA is establishing and maintaining a service governance solution that addresses the complexities inherent in a growing service ecosystem, thereby allowing the business to realize more benefits with less pain.
Ask yourself these questions to see if you might be struggling with some of the most common SOA challenges:
- Can you trust your services to support mission-critical business processes?
- Can you make changes to existing services without wondering what might break?
- Do you have design-time and runtime visibility across all your systems and services?
- Can business, operational, and policy changes be made without requiring developer involvement?
Today, most SOA initiatives cannot answer these questions in the affirmative because they lack a sound strategy for service governance within their SOA. Lacking a focus on service governance can prevent companies from realizing a net gain through their SOA initiatives. Without a governance solution, most SOA initiatives devolve over time into an unwieldy spaghetti mess of services without any sense of management, visibility, or versioning control, and it's only going to be worse as companies move more towards cloud computing over time.
One way to deal with all this complexity is through a governance solution that provides service virtualization. Service virtualization is an emerging trend in the SOA landscape that focuses on providing a common infrastructure for building and managing a complex service ecosystem while addressing the challenges of SOA initiatives. Microsoft Services has been providing leadership in this area through their Microsoft Services SOA Infrastructure offerings and a technical solution referred to as the Managed Services Engine (MSE). First I'll describe what they provide at a very high level before diving into the technical details of how it works.
The Microsoft approach to service virtualization is based on a common architecture and a centralized runtime that provides the service plumbing required by all services and their consumers throughout the entire heterogeneous service ecosystem. This lets developers focus on building new functionality without worrying about how the functionality will be exposed, consumed, and managed over time. The runtime provides the core capabilities all services need such as versioning, protocol-mapping, monitoring, routing, and run-time policy enforcement.
New services are added to the runtime to take advantage of these common runtime capabilities. Services are added to the runtime by simply importing the service metadata through Web Services Description Language (WSDL) definitions, WS-MetadataExchange endpoints, or custom importers for legacy line-of-business apps that describes the service. Once the metadata is imported, administrators can manage the physical service resources by defining models for different types of virtual services that the runtime can automatically host and expose to the various service consumers. The idea is consumers will interact only with the virtual services, which are significantly easier to manage than the real ones.
You define virtual services using models, which are essentially just expressions on top of the original service metadata. Microsoft provides a management tool for defining these models that can be customized for the different SOA stakeholders. The service virtualization runtime translates these models into service behaviors that provide the aforementioned capabilities without requiring any changes to the deployed and running service code. This approach allows you to easily configure virtual service endpoints to accommodate different types of consumers, to version operations, and to modify service policies as business requirements change on the fly.
The service model becomes the communication rendezvous point between all parties involved in the SOA ecosystem—architects, developers, IT professionals, and the business owners—all of whom can interact with the models through role-specific views in the management tool. It's important to note that the service models are actually executed by the service virtualization engine to provide different service behaviors at run time, which helps provide full fidelity with the original intent. Microsoft is moving from a world where models describe the application to a world where models are the application. The new "Oslo" modeling initiative is one example of that, as are these service virtualization techniques used by Microsoft Services.
From a consumer perspective, a virtual service looks just like any other service it might consume. It's a feature that consumers cannot tell the difference between a virtual service and the real service sitting behind the scenes.
A Service Virtualization Implementation Pattern
Service virtualization architectures usually revolve around the idea of a service intermediary sitting between the service client and the service implementation (see Figure 1).
A service intermediary is necessary to decouple the client from the service implementation. In this type of architecture, clients never interact with service implementations directly; they always communicate through the service intermediary. The service intermediary is where the virtual services are hosted and exposed. It can be located with the client, with the implementation, or somewhere in between to provide topology flexibility.
Figure 1 A Common Pattern for Service Virtualization
The service intermediary determines exactly how to host the virtual services by reading the service models from the service catalog. This allows you to expose multiple virtual services for a single service implementation so you can easily accommodate different customer scenarios. You can even define a model for exposing a virtual service in the cloud that relays communication back to your local service implementation. Stakeholders interact with the service catalog through the management tool that provides a role-specific user experience.
Because all communication travels through the service intermediary, it's possible for this central node to intercept each service invocation and introduce additional service behaviors (for example, operation versioning, protocol mapping, monitoring, routing, run-time policy enforcement, and custom behaviors) without having any impact on the client or service code. This service interception pattern is what enables the Microsoft Services SOA Infrastructure solution.
Microsoft Services SOA Infrastructure
Microsoft Services SOA Infrastructure provides an implementation of the service virtualization pattern through a solution called the Managed Services Engine (MSE). The MSE is built on the Microsoft server platform including Microsoft Windows Server 2003 or Microsoft Windows Server 2008, Microsoft SQL Server 2005 or SQL Server 2008, and Microsoft .NET Framework 3.5. There is a heavy focus on Windows Communication Foundation (WCF) for the underlying communication and the implementation of the service interception pattern described earlier. Microsoft Services has made the MSE solution (including source code) available for download via CodePlex. Microsoft Consulting Services (MCS) are available to assist for complex installations and customizations.
Although it's not required, the MSE can also integrate with Microsoft BizTalk Server 2006 R2 to provide additional capabilities such as business activity monitoring for end-to-end visibility, dynamic business rules processing through the Business Rules Engine (BRE), host system integration for service assisting legacy systems, and enterprise service bus (ESB) guidance for durable itinerary-based message routing. Microsoft Services combines the MSE service virtualization technology with these BizTalk Server technologies to offer a compelling SOA infrastructure solution.
The MSE can also make it easier to move toward cloud computing on the Azure Services Platform through integration with .NET Services. As part of .NET Services, the .NET Service Bus makes it possible to publish service endpoints in the cloud that relay communication back to your local on-premises services. You accomplish this by simply using a new set of relay bindings in your endpoint definitions (they ship as part of the .NET Services SDK). The MSE runtime is also built on WCF, which means it can support virtual service endpoints that use the .NET Service Bus relay bindings, thereby allowing you to integrate with virtual service endpoints in the cloud.
Understanding the Managed Services Engine
The MSE comes with a service runtime engine, a service catalog (repository), and a management tool for implementing a real-world service management solution based on service virtualization. It's built on WCF from the ground up, using common techniques and taking advantage of its various extensibility points when necessary.
The MSE runtime engine is implemented as a Windows service that manages a set of WCF service host instances that are automatically configured from the information found in the service catalog at run time. The MSE runtime consists of three logical components internally: the messenger, the broker, and the dispatcher (see Figure 2). The messenger is primarily responsible for message normalization. The broker receives the normalized message and is primarily responsible for operation rationalization (choosing a specific version of a specific operation). The dispatcher is primarily responsible for invoking the target implementation.
Figure 2 The Managed Services Engine Architecture
These three components are completely decoupled from one another since all communication happens via WCF channels. Hence, you can distribute them across multiple servers in order to accommodate a wide variety of system topologies that your environment might require.
The service catalog is often referred to as the metadata repository—you can think of it as the source of truth across an MSE environment and the participating systems. The catalog contains the models that drive your virtual services hosted by the MSE runtime. You import services (as well as other artifacts) into the repository using a management tool. Once you've imported the service metadata, you can begin defining what your virtual services will look like to the rest of the world.
When defining a virtual service, you start by choosing and configuring a WCF binding. You can choose what protocol to use, what security features to enable, or any other setting supported by the binding. You can also specify other service behaviors like message inspection, message transformation, version information, and what policies to enforce (including custom policies) using the same technique.
The metadata repository is implemented as a traditional SQL Server database. Consumers don't interact directly with the repository, although information from the repository can be published to an external registry (such as UDDI) for discovery purposes. The MSE maintains a clear distinction between the notion of a repository and public registry. The repository is the source of truth for data that drives the runtime, whereas the registry is a separate store of service metadata that you decide to make available for external consumption and discovery.
The MSE allows for independent ownership of each layer within the MSE environment. This is important because in most SOA environments, managing the overall ecosystem is a team effort with numerous roles involved. For example, developers usually define the data entities, operation contracts, and implementation logic, whereas the IT operations staff dictates service instance configuration, endpoint policies, and messaging standards. Architects can influence service policies and define execution patterns while business owners dictate business policy requirements. The MSE makes it possible for all stakeholders to collaborate with one another through the MSE catalog, which acts as a central rendezvous point for the system. And the MSE runtime dynamically executes those models to influence the runtime behavior of each layer within the configured MSE environment.
Getting Started with the MSE
In order to get started with the MSE, you'll first need to download and install it on one of your development machines. The installation will set up and configure the various components described in the previous section.
The installer will create and populate a SQL Server database called MSE_REPOSITORY. (The MSE supports both SQL Server 2008 and SQL Server 2005.) The repository database is where the service catalog information is stored that drives the runtime and that ultimately shows up in the management tool. If you poke around, you'll find various tables for holding data entities, operations, endpoints, policies, and runtime servers.
The installer also sets up Windows services that host the MSE runtime and catalog logic. After installation, you'll find a Windows service called MSE Runtime Server and another called MSE Catalog Server. The MSE Runtime Server service is the process that ultimately hosts your virtual service endpoints and provides the runtime messenger/broker/dispatcher logic. The MSE Catalog Server provides an interface to underlying service catalog.
You'll also find a new program group in your Start menu labeled Microsoft Managed Services Engine—Management Tool. If you expand the group, you'll find two programs: the MSE Administration Tool and the MSE Service Tester. The former is the management tool I've been referring to up to this point while the latter is a generic service testing tool that makes it easy to test the virtual service endpoints hosted in the MSE runtime.
Figure 3 shows what the MSE Administration Tool looks like the first time you launch it. This is showing what's referred to as the end-to-end view of the MSE system. The six areas spanning the top row show the end-to-end execution path starting from the virtual MSE runtime servers (left) to the real service systems (right). Figure 4 provides a brief description of each section shown in the MSE Administration Tool's end-to-end view.
Figure 3 The MSE Administration Tool
|Figure 4 MSE End-to-End View Elements
||Describes the servers hosting the real services (that you wish to virtualize)
||Describes the individual service endpoints running on a particular system
||Describes the service operations supported by a particular service "instance"
||Describes virtual service operations that map to specific resources
||Describes virtual service endpoints that support a set of specific virtual operations
||Describes the MSE runtime servers hosting the virtual endpoints
||Describes a set of binding configurations used by instances and endpoints
||Describes a set of policies that can be used by most artifacts
||Describes a set of data entities that are used to define operation contracts
The Layout menu allows you to select different views of the system that simplify the experience for different types of SOA stakeholders. Each system artifact within the MSE models can be assigned an owner where you can specify name, contact, and location information. This owner information can be viewed and modified by the different stakeholders to figure out who to talk to when issues arise throughout the SOA lifecycle.
We don't currently have any virtual services defined, but the catalog does come prepopulated with some default configurations. In order to get started, you need to import your service resources into the catalog.
Importing Service Resources from WSDL
Before you can start creating virtual services in the MSE, you must first populate the repository with metadata describing the real service resources that you intend to virtualize. Since the MSE will be invoking the real service instance on behalf of consumers, it needs to know what address it's listening on, the specific WCF binding configuration to use, and what the request/response messages will contain (basically the information you'll find in a service's WSDL definition). You can enter this information manually for a specific service or you can import it from the service's WSDL definition using one of the wizards provided by the MSE Administration Tool. Since the latter approach is more common, let's walk through an example of importing a new service resource.
You import new service resources through the MSE Import Wizard, which you can launch from the Tools menu by selecting Load Resource | Load Web Service Metadata. The wizard will first ask for you to specify the location of the WSDL definition describing the service by either specifying a URL or a path to a local file.
Then the wizard will ask you what type of service metadata you'd like to extract from the WSDL definition to import into the repository. You can choose to import only the schema definitions, and they will be imported as schemas and data entities in the MSE repository. You can also choose to import all of the service resources found in the WSDL definition including the schemas, operations, bindings, and endpoints, and they will be imported into the MSE repository as data entities, resources, bindings, and instances, respectively.
There's yet another option that imports all service resources (like the second option) but it also generates a virtual service operation for each operation found in the service metadata. This option can be useful to speed things up when your service contains a lot of operations, but it's not necessary since there's also a separate wizard for defining virtual service operations. I'll show you how to create new virtual operations a bit later.
At this point, the wizard will show you everything it plans to import, giving you an opportunity to exclude certain items if you so choose. It will also show you if the item already happens to exist in the MSE catalog, indicating that you don't need to import a particular item again. If you selected the option to generate virtual operations, you'll also see a summary of the virtual operations it plans to import into the catalog.
You can modify the names of your virtual service operations during this step—I've prefixed each operation name with "Virtual" in my example (VirtualGetPizzaMenu, VirtualGetPizza, and so on). Figure 5 shows the summary of the virtual operations it's going to define and map to the real service operations. Notice how the operation name is extended with an operation version. In this case, the initial versions are 126.96.36.199. As newer versions of operations are incorporated, the operation name remains the same but the version is incremented as needed.
Figure 5 Specifying Virtual Operations in the MSE Import Wizard
It's interesting to note that the MSE repository doesn't contain the definition of a service contract as you will find in a WSDL definition. Instead the model revolves around service operations. The model allows you to arbitrarily compose service operations onto endpoints however you see fit, dynamically creating different contract configurations at run time. This also means you have to worry about versioning only at the operation and data entity levels, which is possible because the service contract itself is never represented in the messages at run time.
Once you confirm the import, the wizard will bring the selected items into the MSE repository and now you're ready to start defining some virtual service endpoints to expose your virtual operations.
Importing a RESTful Service Resource
The MSE also makes it possible to virtualize RESTful services. However, since RESTful services don't typically come with a WSDL definition, you'll have to use a different wizard for defining and importing metadata that describes the RESTful service in terms of operations. You can begin this process by selecting Load Resources | REST Service Metadata from the Tools menu. Doing so launches the RESTful Service Virtualization Wizard.
This wizard starts by asking you to specify the name of the resource exposed by the RESTful service along with the base URI for the resource. Then it asks you to specify the HTTP verbs (GET, POST, PUT, or DELETE) and content type (for example, text/xml, application/json, or application/atom+xml) supported by the resource. It will also ask you to specify a set of resource attributes that can be used in conjunction with the different verbs (see Figure 6). The resource attributes are basically the parameters required by the different HTTP verbs.
Figure 6 Specifying Resource Metadata for a RESTful Service
The next step in the wizard allows you to define the URL contract for each of the HTTP verbs supported on the resource. This is where you specify the UriTemplate pattern, including any URI variables—the URI variables should map to the resource attributes you defined in the previous step (see Figure 6).
Once you finish specifying all of these details, you can complete the wizard to import the metadata for that particular resource. You'll need to repeat the wizard for each resource exposed by the RESTful service. The MSE runtime will be able to use this information to figure out how to interact with each of the different REST resources at run time.
After you've finished importing all of your REST resources using the wizard, you can define virtual operations for them, making it possible for consumers to interact with them through the MSE.
You can define a virtual operation by running the Virtual Operation Creation Wizard (right-click in the Operations area and select Add New Item). This wizard allows you to specify the name and version of the virtual operation, along with a human readable description. You can also specify if the virtual operation is public, active, or testable (see Figure 7). A public operation is discoverable via the virtual service endpoint or MSE-associated registry. An active operation can be invoked through the MSE (for example, you might have five different versions of an operation that are all active, but only one that's publicly discoverable). A testable operation can be called even if it's not backed by a real service implementation (the MSE represents a sample implementation, making it possible to test consumers before the service has been deployed).
Figure 7 Virtual Operation Creation Wizard
The next step of the wizard will ask you to specify what data entities to use for the request/response. Then you'll have a chance to map the virtual operation to a real service resource—this is where you'll map it to the REST resource defined using the RESTful Service Virtualization Wizard. This essentially tells the MSE runtime which service instance to execute in response to calls targeting this virtual operation.
It's important to note that when you import the SOAP service resource using WSDL, the wizard takes care of creating default virtual operations for you (if you select the Virtual Service Operations option in the wizard). Hence, you don't have to do this step manually. You'll always have to do this manually for RESTful services because they don't provide WSDL metadata that can be used to automate things.
Figure 8 shows what the MSE Administration Tool looks like after importing both the SOAP and REST operations. Notice the four virtual operations, each of which maps to a real service resource that will be executed by the MSE runtime. Each service resource maps to a service instance hosted on a backend system. At this point, you're ready to expose some virtual service endpoints through an MSE runtime server.
Figure 8 MSE Administration Tool after Importing Services
Defining Virtual Service Endpoints
The MSE Administration Tool makes it easy to define virtual service endpoints. Simply right-click on the Endpoints area and select Add New Item. This will give you an interface for defining a WCF endpoint definition. You specify the port and path for the endpoint address (but not the server, since that's controlled separately by the server configuration), along with the binding configuration you want to use. You specify the binding configuration by dragging one of the items from the binding configurations area into the form. Then you can associate virtual operations with the virtual endpoint by dragging the virtual operations onto the endpoint form (see Figure 9). The operation associations are what ultimately define the service contract for the virtual endpoint.
Figure 9 Defi ning a Virtual Endpoint
You can create as many virtual endpoints as you need this way, making it possible to accommodate a wide variety of different consumers with differing communication requirements. As an example, I created three different virtual endpoints: one using the WSHttpBinding (SOAP 1.2), another using the NetTcpBinding, and yet another using a POX binding (no SOAP).
Once you have the different virtual endpoints defined, you can choose which virtual operations you'd like to associate with each one. For this example, I can associate all of the virtual operations with each of my virtual endpoint definitions. This means I can invoke all four virtual operations through each endpoint.
After you've defined your virtual endpoints, the last thing you have to do is associate them with a configured MSE runtime server. You configure the runtime server instances within the Servers area in the MSE Administration Tool. They provide a default runtime server configuration called LocalRS when you install the MSE. You can publish virtual endpoints on this default runtime server by simply dragging them onto the LocalRS icon.
These virtual endpoints are now active and ready to be called by consumers. At this point, you can browse to any of the virtual endpoint addresses and retrieve its metadata (for example, http://localhost:10002/vpizza/ws?wsdl). Consumers can then download the metadata and build client applications against the MSE virtual service. You can also bring up the MSE Universal Service Tester (from the Start menu) and enter the WSDL address of the virtual service. This tool makes it easy to test your virtual service endpoints without writing a client (see Figure 10).
Figure 10 Using the MSE Universal Service Tester
When a consumer invokes one of the virtual operations, the MSE runtime will dispatch the call to the corresponding service resource behind the scenes. During this process, the MSE maps the virtual operation invocation to the real service resource behind the scenes.
During the invocation process, the MSE runtime provides support for operation versioning and runtime policy enforcement to enable things like message transformation, message validation, custom service behaviors, and business activity tracking and monitoring. All of these things are easy to configure via the MSE Administration Tool.
Versioning, Policies, and WCF Behaviors
One of the most difficult things to manage in real-world SOA environments is service versioning. Through service virtualization, the MSE is able to provide a versioning solution that is intuitive and easy to manage. When you need to version a service operation, you simply define a new virtual operation with an incremented version number. Then you make whatever adjustments you need to make to the new virtual operation version. The virtual operation will show up only once in the virtual operation list, but it will contain two version configurations.
When you drag the versioned operation onto a virtual endpoint, it will ask you which of the versions available you'd like to make active and which version you'd like to publish. Multiple versions can be active at the same time, but only a single version can be published to the registry—typically the latest version—due to the inherent restrictions of the WSDL specification. Similar versioning support is also provided at the data entity level in the MSE. In general, having a built-in versioning solution is critical for managing large-scale SOA environments as they grow over time.
In addition to service versioning, the other key feature provided by the MSE is dynamic policy configuration. The MSE formalizes the concept of policies, policy assertions, and policy assertion types, which behind the scenes end up mapping to WCF behaviors that can extend your virtual services in a variety of different ways. If it can be done with a WCF behavior, you can map it to a declarative policy for use within the MSE environment.
You can import existing WCF behaviors into the MSE catalog through the Policy Import Wizard (select Tools | Load Policy Components | WCF Behavior Assertion Types). This wizard walks you through the process of creating a new policy assertion that maps to a WCF behavior and creates the necessary policy assertion types within the MSE catalog. Once you've done this, you can easily apply policies to systems, instances, resources, virtual operations, and virtual endpoints by simply dragging the policies onto those items within the MSE Administration Tool.
For example, you can define a policy that performs message transformations to enable a more intelligent mechanism for mapping between versioned operations. You can also define a policy that performs custom schema validation against the incoming messages in a standard way. Or you can define polices that introduce custom security, tracking, and logging behaviors. The Microsoft Services SOA Infrastructure solution comes with built-in polices for enabling business activity monitoring across your virtual services hosted by the MSE along with reports for viewing the data.
Defining Virtual Cloud Endpoints
Since both the MSE and the .NET Service Bus are built on the WCF binding/channel model, it's easy to bring these two worlds together. Doing so makes it possible to enable two key cloud scenarios illustrated in Figure 11.
Figure 11 Service Virtualization in the Cloud
First, you can virtualize .NET Service Bus endpoints through the MSE. This means you can create different virtual endpoints in the MSE that act as facades for a .NET Service Bus endpoint. When consumers invoke one of the virtual service endpoints, the MSE will invoke the corresponding .NET Service Bus endpoint in the cloud, which relays the message to an on-premises service somewhere in the world. (It relays the message to any listeners registered with that address on the .NET Service Bus.)
Second, you can create virtual service endpoints hosted by the MSE that actually listen on the .NET Service Bus. In this case, the .NET Service Bus endpoints relay to the MSE runtime server, which in turn relays to a real service instance managed by MSE.
To begin integrating your MSE installation with .NET Services, you'll need to install the .NET Services SDK on the MSE server. Then you'll need to define a custom binding within the MSE Administration Tool for each of the .NET Service Bus relay bindings you wish to use within the MSE catalog. You can do this by switching to the Binding Management layout and adding a new binding configuration. Here you can enter a name for your new binding configuration (such as NetTcpRelay) along with the actual XML binding configuration (the same format you'd use in a WCF configuration file). This is the XML for the default NetTcpRelayBinding configuration:
Once you've defined all of the relay bindings you plan to use, you can begin using them in conjunction with the other MSE artifacts. For example, to configure the MSE to call the .NET Service Bus, you'd first create a new MSE system using the address to the .NET Service Bus
(). Then you'll define a new instance for the .NET Service Bus, specifying the solution name to use in the URL, and you'll map that to the .NET Service Bus system you just defined. Then you'll need to leverage the .NET Service Bus policy that supplies the .NET Service Bus credentials at run time, and you'll associate the policy with the instance.
Once you're finished, you can begin defining MSE resources that route to the .NET Service Bus instance. Then you can define virtual operations and endpoints that map to the .NET Service Bus resources.
You can also define MSE virtual endpoints that use the .NET Service Bus relay bindings that you've added to the catalog. For the virtual endpoints, you'll need to specify the appropriate .NET Service Bus path (/services/[solution-name]/[path]) and define a new server instance that refers to the .NET Service Bus host
. The MSE runtime will register a listener on the .NET Service Bus for each virtual endpoint, allowing the MSE runtime to receive relayed messages from the cloud.
As you can see, by using the MSE service virtualization technology, you can begin participating in the cloud by simply configuring virtual service endpoints and operations that integrate with the .NET Service Bus.
Service virtualization will help reduce your time-to-market for new services on-premises or in the cloud, and it will provide a more flexible and realistic approach for managing your service ecosystem as it grows and matures over time. The bottom line: service virtualization can help you realize the full benefits SOA has to offer your business.
is a Microsoft MVP and 10-year columnist for MSDN Magazine
as well as co-founder of Pluralsight, an industry think-tank where he stays busy recording Pluralsight On-Demand! courses on Windows Azure, WCF, and REST. Reach him at http://pluralsight.com/aaron
and follow him on http://twitter.com/skonnard