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 .
- 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
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!
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 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,
await, 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.
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.
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.
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.
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 (
0xfffffffemilliseconds) in the future
- Reminders can only tick at an interval of \(\leq\)49 days (
- 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.
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.
For the Orleans Silo Cluster, we’ll be using OrleansDashboard to monitor our silos.
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.
Redis will be used for distributed caching for API requests.
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.
We’ll be using GitHub Actions to build, test, package, and deploy our services as container images to DigitalOcean’s App Platform.
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.