A great guide for developing a Software Architecture Document (SAD). Click link above. At a high level, the SAD includes:
Posts tagged Architecture
Windows Service Bus offers a powerful durable messaging backbone for distributed and cross platform systems. This is a quick example how to use it to send messages across machines using Topics and Subscriptions.
Example Scenario: employees of service provider X need to be able to manage styles for their customer branded applications and build new applications in real time.
The old way: employees would save branding/styles and then manually build a new version of applications that use the styles. This was a costly and time consuming process.
The solution: automate the building of the applications every time styles are saved/updated. Use service bus to provide a messaging mechanism that will link the web front end application with the build server.
Sequence Diagram
Initialize Topics & Subscriptions
The first thing we need to do is ensure the topics we want to send messages on exist and the subscriptions we want to use also exist. Here is a simple initialization method we call to ensure these are set up.
public static void InitializeServiceBus(NamespaceManager namespaceManager) { if (!namespaceManager.TopicExists(Constants.Topics.BuildRequestTopic)) namespaceManager.CreateTopic(Constants.Topics.BuildRequestTopic); if (!namespaceManager.TopicExists(Constants.Topics.BuildResponseTopic)) namespaceManager.CreateTopic(Constants.Topics.BuildResponseTopic); if (!namespaceManager.SubscriptionExists(Constants.Topics.BuildRequestTopic, Constants.Subscriptions.BuildRequestSubscription)) namespaceManager.CreateSubscription(Constants.Topics.BuildRequestTopic, Constants.Subscriptions.BuildRequestSubscription); if (!namespaceManager.SubscriptionExists(Constants.Topics.BuildResponseTopic, Constants.Subscriptions.BuildResponseSubscription)) namespaceManager.CreateSubscription(Constants.Topics.BuildResponseTopic, Constants.Subscriptions.BuildResponseSubscription); }
Topics in Azure
Style Controller
Using ASP.Net MVC we’ll create a controller that will handle the “save changes” event. This will capture the new style and send a “build request” to the service bus.
public class HomeController : Controller { private readonly NamespaceManager namespaceManager; private readonly TopicClient buildRequestTopic; private readonly IStorageProvider storageProvider;
public HomeController() { namespaceManager = NamespaceManager.Create(); InitializeServiceBus(namespaceManager); buildRequestTopic = TopicClient.Create("BuildRequestTopic"); storageProvider = new FileSystemStorageProvider(); } private void Save() { var tenant = Request["tenant"]; var css = Request["css"]; var cssData = Encoding.Default.GetBytes(css); var bundleId = storageProvider.Store(cssData, tenant); var message = new BrokeredMessage(); message.Properties.Add("bundleId", bundleId); message.Properties.Add("tenant", tenant); buildRequestTopic.Send(message); } }
You can see the code above uses the NamespaceManager and TopicClient to create a topic to send requests on. The HTML page will provide a simple UI to change the CSS for a given tenant, and a way to kick off the build of the application based on the changed CSS.
UI to Manage Style
When the build button is clicked the “Save” method in the code snippet above is called, and a message is sent to the service bus. Another process (the “Worker” process) will pick up that event and process the change as a new build.
The Worker Program
The worker code is very simple. It basically just starts up a subscription and listens for build events, then responds to a build event by building an application, storing it, and sending back a build done event.
public static void Main() { IStorageProvider storageProvider = new FileSystemStorageProvider(); var namespaceManager = NamespaceManager.Create(); InitializeServiceBus(namespaceManager); var buildResponseTopic = TopicClient.Create(Constants.Topics.BuildResponseTopic); var client = SubscriptionClient.Create(Constants.Topics.BuildRequestTopic, Constants.Subscriptions.BuildRequestSubscription); client.Receive(); while (true) { var message = client.Receive(); if (message != null) { try { var bundleId = (string)message.Properties[Constants.Properties.BundleId]; var tenant = (string)message.Properties[Constants.Properties.Tenant]; Console.WriteLine("Got bundleId: " + bundleId + ", for tenant: " + tenant); var cssData = storageProvider.Get(bundleId); var css = Encoding.Default.GetString(cssData); var appBuild = BuildApplication(css, tenant); var appBuildId = storageProvider.Store(appBuild, tenant); Console.WriteLine("Built application, buildId: " + appBuildId); var response = new BrokeredMessage(); response.Properties.Add(Constants.Properties.BundleId, bundleId); response.Properties.Add(Constants.Properties.BuildId, appBuildId); buildResponseTopic.Send(response); // Remove message from subscription message.Complete(); } catch (Exception) { // Indicate a problem, unlock message in subscription message.Abandon(); } } } }
Push Notification (Build Done Event)
Finally if we switch back to the UI application, we’ll notice a separate thread is started in the Global.asax “Application_Start” method.
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); (new Thread(ListenForBuildResponses)).Start(); }
This will start a background thread to listen for build finished events, and notify the UI when a build is done (via Signalr)
private void ListenForBuildResponses() { var namespaceManager = NamespaceManager.Create(); Core.Utilites.InitializeServiceBus(namespaceManager); var client = SubscriptionClient .Create(Core.Constants.Topics.BuildResponseTopic, Core.Constants.Subscriptions .BuildResponseSubscription); client.Receive(); while (true) { var message = client.Receive(); if (message != null) { try { var buildId = (string) message.Properties["buildId"]; var hubContext = GlobalHost.ConnectionManager.GetHubContext(); hubContext.Clients.All.buildDone(buildId); // Remove message from subscription message.Complete(); } catch (Exception ex) { // Indicate a problem, unlock message in subscription message.Abandon(); } } } }
Finally we add a little JavaScript to the HTML page so SignalR can push a build done notification to the UI.
$(function () { var notificationHub = $.connection.notificationHub; notificationHub.client.buildDone = function (buildId) { $("#target") .find('ul') .append($("").html("Build Done: " + buildId + "")); }; $.connection.hub.start(); });
At this point the UI displays the download link and the user downloads the newly built application.
Part of building stateless systems that scale horizontally is using a distributed cache (where state is actually stored). This guide outlines the different types of items one will probably need to cache in a system like this, where to cache it (local or distributed), how to use the cache, what timeouts to use, etc.
Horizontal Scale
First lets review what a horizontally scaled system looks like.
Machine 1 and 2 accept requests from the load balancer in an un-deterministic way, meaning there is no affinity or sticky sessions. So the machines need to be stateless, meaning they don’t manage state themselves. The state is stored in a central place, the distributed cache. The machines can be taken offline without killing a bunch of user session, and more machines can be added and load can be distributed as needed.
Types of Cache
Notice there are two types of caches here:
1) Local caches - these are in-memory caches on each machine. This is where we want to store stuff that has long running timeouts and is not session specific.
2) Distributed cache - this is a hi performance cluster of machines with a lot of memory, built specifically for providing a out of process memory store for other machines/services to use. This is where we want to sure stuff that is session specific.
Using Cache
When using information that is cached, one should always try to get the information from the cache first, then get from source if it’s not cached, store in cache, and return to caller. This pattern is called the read through cache pattern. This ensures you are always getting data in the most efficient means possible, before going back to the source if needed.
Cached Item Lifespan
There are basically two things to think about when thinking about cached items lifespan.
1) How long should something remain in the cache before it has to be updated? This will vary depending on the type of data cached. Some things like the logo of a tenant on a multi-tenant system should have a long timeout (like hours or days). While other stuff like an array of balances in a banking system should have a short timeout (like seconds or minutes) so it is almost always up-to-date.
2) When should stuff be removed from cache? You should always remove stuff from cache if you know you are about to do something that will invalidate the information previously cached. This means if you are about to execute a transfer, you should invalidate the balances because you’ll want to get the latest balances from the source after the transfer has happened (since an update has occurred). Basically any time you can identify an action that will invalidate (make inconsistent) something in the cache, you should remove that item, so it can be refreshed.
Designing Cache Keys
You should take the time to design a good cache key strategy. The strategy should make it clear for your development team how keys are constructed. I’ll present one way to do this (but not the only way). First think about the types of data you’ll be caching. Lets say a typical multi-tenant system will consist of the following categories of cached items:
1) Application- this is stuff that applies to the whole system/application.
2) Tenant - this is stuff that is specific to a tenant. A tenant is a specific organization/company that is running software in your system.
3) Session - this is stuff that is specific to a session. A session is what a specific user of an organization creates and uses as they interact with your software.
The whole point of key design is to figure out how to develop unique keys. So lets start with the categories. We can do something simple like Application = “A”, Tenant = “T”, Session = “S”. The category becomes the fist part of the cache key.
We can use nested static classes to define parts of the key, starting with the categories. In the code sample above we start with a Application class that uses “A” as the KeyPattern. The next we build a nested class
"Currencies" which extends the KeyPattern with it’s own unique signature. Notice that the signature in this case takes in parameters as to create the unique key. In this case we are using page and page size to build the key. This way we can cache a specific set of results to a query that uses paging. There is also a property to get the TimeToLive and another to construct the key, based off the pattern.
The above example is caching stuff in a “local cache”, not a distributed cache. This is because the information in this example is not specific to a user or session. So it can be loaded on each machine which can keep a copy of it there. Generally you want to do this for anything that doesn’t need to be distributed, because it performs much better (think local memory vs. serialization/deserialization/network, etc).
When thinking about unique keys for things like session, you should consider putting the session identifier as an input to the key, since that should guarantee uniqueness (per session). Remember you basically just have a really big name/value dictionary to fill up. But you have to manage the uniqueness of the keys.
Takeaways
1) Use both a local and distributed cache. Only put session or short lived stuff in the distributed cache, other stuff cache locally.
2) Set appropriate timeouts for items. This will vary depending on the type of information and how close to the source it needs to be.
3) Remove stuff from cache when you know it will be inconsistent (like updates, deletes, etc).
4) Take care to design cache keys that are unique. Build a model of the type of information you plan to cache and use that as a template for building keys.