This is the second post in the series, if you haven’t read the first, you should stop here and read the first part before continuing.
This post covers the system design of the Web Scheduler and the decisions made during the design process.
The code for the project can be found here in these repositories .
The Tech Stack
- Hosting Platform: DigitalOcean’s App Platform
- Web Frontend Application: ASP.NET Core Blazor
- Frontend API: ASP.NET Core Web API
- Backend: Microsoft Orleans
- Durable Data Persistence: MySQL
- Caching: Redis
- Claims-based Identity: Duende IdentityServer with ASP.NET Core Identity
- Build, Test, Package, and Containerize: GitHub Actions
- Observability: OpenTelemetry and Jaegar
DigitalOcean’s App Platform
The WebScheduler will be deployed to DigitalOcean’s App Platform and leveraging their managed PaaS offerings for support services.
App Platform is a fully-managed solution that allows you to build, deploy, and scale your apps so you can focus on what you do best: building great software!
The WebScheduler will be deployed as an App Platform App using different component types: Database , Service , and Static Site .
Our WebScheduler App will be deployed as depicted below. You can click each component to jump to the relevant portion of the App Spec .
Below are the details about each one of the components that make up the WebScheduler App.
Microsoft Orleans
Microsoft Orleans is an open-source, cross-platform framework for building robust, scalable distributed applications in .NET. When I discovered Orleans sometime around 2015 I fell in love with it. It is the type of technology that one can get really excited about, the kind that changes mental model of problems and solutions. I’ve built a few solutions over the years with Orleans but have recently begun making real contributions (non-docs 😅) to the project.
If you’re interested, join the Official Orleans Discord chat ™️, it’s by far the most welcoming project community I’ve ever been a part of!
I don’t think there is a better explanation of what Orleans is than what has already been written in the Orleans documentation :
Orleans is a cross-platform framework for building robust, scalable distributed applications. It builds on the developer productivity of .NET and brings it to the world of distributed applications, such as cloud services. Orleans scales from a single on-premises server to globally distributed, highly-available applications in the cloud.
Orleans extends familiar concepts like objects, interfaces,
async
andawait
, and try/catch to multi-server environments. Accordingly, it helps developers experienced with single-server applications transition to building resilient, scalable cloud services and other distributed applications. For this reason, Orleans has often been referred to as “Distributed .NET”.It was created by Microsoft Research and introduced the Virtual Actor Model as a novel approach to building a new generation of distributed systems for the Cloud era. The core contribution of Orleans is its programming model which tames the complexity inherent to highly-parallel distributed systems without restricting capabilities or imposing onerous constraints on the developer.
Check out An Introduction to Orleans from Microsoft Reactor for a great primer.
Orleans History
Note: this section was pieced together from various Orleans blog posts and other sources. If there is more to it, or any inaccuracies, let me know and I’ll amend this section.
Orleans started out in Microsoft Research in 2008 and was quickly moved into the Xbox organization where it lived most of it’s life. It is being used for games such as 343 Industries ’s Halo and Epic Games’ Gears of War series. Orleans is also being used by a number of internal teams at Microsoft to build Azure ML , Azure IoT Digital Twins , Microsoft Mesh , Azure Quantum , Microsoft Dynamics 365 Fraud Protection , Azure PlayFab (part of Xbox), just to name a few. For more details, check out the Orleans at Microsoft video, where Reuben Bond talks about how Orleans is used at Microsoft.
More recently, Orleans has moved into the Microsoft Development Division (DevDiv). DevDiv is the division in Microsoft which is responsible for developer tooling, languages, frameworks, and some cloud services. This is an exciting time for Orleans and I’m super excited to see what this move means for the growing Orleans ecosystem, especially since it’ll be closer aligned with other .NET open-source technologies.
Implementation Details
Orleans manages our concurrency and load balancing across Grains for us. These are attractive features for us as we want to eliminate the need to manage these seemingly simple, but very complex and nuanced concerns ourselves.
We’ll be modeling our Scheduled Tasks as a Grain running on the Orleans cluster. Each ScheduledTaskGrain
will have a unique ID and will be responsible for executing the task at a specified time. The grain state
is modeled in ScheduledTaskMetadata
, which is a POCO
. Scheduled tasks will leverage the Orleans Reminders
feature to schedule their execution.
The ScheduledTaskGrain
The ScheduledTaskGrain
is the workhorse of the Scheduler. It is responsible for managing the state of the ScheduledTaskGrain
and for triggering the executing of the scheduled task per the desired schedule..
The grain acts as a CRUD interface for the ScheduledTaskMetadata
state. The ScheduledTaskMetadata
, as the name implies, holds all of the information required to schedule and execute the task.
Scheduling the Task
Orleans has a mechanism called Reminders which enable you to specify periodic tasks that are executed by a grain. Reminders are durable and the Orleans runtime guarantees reminders will always be fired. This is a great way to implement a cron-like scheduling system, except they don’t exactly allow for cron-like scheduling…yet !
The primary reason for selecting Orleans is because of the durable reminders that scale near-linearly when adding new Silos to the cluster.
Reminders do have some limitations:
- Reminders can only scheduled \(\leq\)49 days (
0xfffffffe
milliseconds) in the future - Reminders can only tick at an interval of \(\leq\)49 days (
0xfffffffe
milliseconds) - They don’t speak crontab
- There is no way to have a task run only once. It’ll keep ticking at the specified interval until you unregister the reminder .
We’ll be working around all of these limitations as part of the implementation.
Executing the Task
The type of trigger is determined by the TriggerType
enum value stored in in ScheduledTaskMetadata.TaskTriggerType
. For now, we’re going implement a simple trigger, the TaskTriggerType.HttpTrigger
, supporting only HTTP GET
requests. The ScheduledTaskMetadata.HttpTriggerProperties
holds information to use for the actual HTTP Request.
In the future, we’ll be adding support for all other HTTP verbs and message queueing systems such as RabbitMQ or Kafka. configuration for HTTP request task types.
Observability
For the Orleans Silo Cluster, we’ll be using OrleansDashboard to monitor our silos.
OpenTelemetry and Jaeger
We’ll be using OpenTelemetry and Jaeger to implement distributed tracing, so we can see what’s happening in our services.
ASP.NET Core Blazor
ASP.NET Core Blazor is a framework for building interactive client-side web UI using .NET instead of JavaScript. Blazor has two flavors (soon three with Blazor Hybrid ), Blazor WebAssembly and Blazor Server .
Blazor WebAssembly is a client-side framework that uses the HTML, CSS, and JavaScript APIs of the browser to render the UI and executes as Web WebAssembly directly in a .NET framework runtime in the browser.
Blazor Server is a client and server-side framework where UI updates are processed server-side and handled over a SignalR connection.
For this project, I wanted to keep the UI relatively simple without a backend-host so I could leverage App Platform’s Static Site component . To meet this objective, I chose Blazor WebAssembly.
For a UI Component library, We’ll be using Blazorise with the Bootstrap5 theme .
MySQL
For grain storage and other orleans data persistence, We’ll be using MySQL , see ADO.NET grain persistence .
Redis
Redis will be used for distributed caching for API requests.
Duende IdentityServer with ASP.NET Core Identity
We’ll be supporting both Social logins and local logins. To do so, be implementing Duende IdentityServer to handle our authentication with JWT with claims-based authorization to secure our ASP.NET Web APIs and Frontend Blazor App.
GitHub Actions
We’ll be using GitHub Actions to build, test, package, and deploy our services as container images to DigitalOcean’s App Platform.
Tying it All Together
The components of the WebScheduler system.
The next article in the series will focus on how to consume the Web Scheduler and reuse it within your own system.
Comments