Author: Pablo Diaz
Recently I wanted to get my hands on two challenges that I have seen are quite
popular: how to handle concurrent requests over shared-limited resources and how to send real-time notifications to browser-based users; I know, these topics do not relate to each other directly, however, these are two of the ones I face in my current project.
So, I was thinking how I could create a PoC that would let me prove some technologies and provide a useful solution to each challenge and I came up with a web application that allows users to attend SCRUM user story refinement meetings and to submit their votes concurrently while being online attending these refinement sessions.
Code is here if you want to look: https://github.com/pablo-diaz/refinement-voting
Let us see how this web application would allow me to provide solutions to each challenge:
Challenge #1: Concurrency
Users of this application would:
- Concurrently join to a (possible limited) voting room
- Concurrently submit their refinement votes when session is setup to allow it
To tackle this challenge, I was thinking about using the Actor Pattern because it would allow concurrent requests to be processed individually as they occur, so that I do not need to introduce special thread-locking strategies on limited resources (voting room) and I can also enforce business rules over these concurrently-accessed resources, such as users joined to the same voting room should not ever have the same name.
So, I decided to use Proto.Actor as an implementation of this actor pattern and use ASP.Net to expose some endpoints that would allow its callers to interact with this actor model.
I left user security and persistence out of this PoC, because those were not challenges I wanted to tackle with this PoC, however, we can always adjust this code to be enhanced with such requirements.
The high-level design of this actor model looks like this:
Actor model strategy:
- Model state is always kept in memory
- There is only one single Room Manager actor, which:
- Keeps track of all Rooms that are active in the system
- Forwards all incoming messages to the right Room actor
- Spans new Room actors as these are required to be created
- Removes idle Room actors
- A given Room actor:
- Keeps track of its current status, so invariants can be enforced
- Transitions to other statuses, as messages are processed, such as when a new voting session has been started by the leader, or when she decides to finish that voting session
- Keeps track of all Member actors that have joined to this voting room
- Spans new Member actors as they join to this voting room, enforcing uniquely named member rules
- Forwards messages to the right Member actor
- Send messages to each Member actor of the room as events (messages being processed) have happened
- A given Member actor communicates event messages to the browser user that is bound to this Member actor. This communication strategy will be explained in the next section
The benefits I saw about this strategy of using the actor model pattern to tackle this challenge, were:
- It allowed concurrent requests to be handled easier
- It allowed a shared-in-memory model to be transitioning between different states, without needing to worry about how to temporally lock limited resources (voting rooms), now that each request was being handled independently, because of the nature of the actor model
- Memory footprint was incredibly low, so this solution could scale vertically, without needing too many memory resources, compared to another solutions
- Separating concerns/responsibilities to each actor increases readability and maintainability
Challenge #2: Send real time notifications to browser
Users joined to each voting room needed to be notified about real-time events that were happening in their joined room, so that a user would always know:
- Other peers: Who has joined (and is joining) to the same voting room she has joined
- When the voting room leader has decided to start a new voting session in that specific room
- When peers are submitting their user story refinement votes
- When the leader has decided to conclude the voting session (before starting a new one later), so that voting results can be displayed to everyone at the same time
Communicating events to the browser user: I was initially thinking about bringing web sockets to the scene, however, as I was tackling the challenge of sending pushed notifications as these events were happening in the actor model, using web sockets was too much over-engineering, as communication was only happening in one direction, i.e. from the server side to the client/browser side. So, I decided to use Server Sent Events as the communication strategy, so that once a user has joined a specific voting room, a background task in the browser would join to the event stream of that specific room, to listen to these notifications as these events were happening.
Benefits of using SSE to push these real-time event notifications to the browser:
- From the frontend/browser perspective, it was easy to implement, as the “EventSource” browser capability is mature enough nowadays in most of today’s modern browsers
- Each user would receive only their independent events that were meant to be sent only to them
- From the backend perspective, long-running http requests (that last as long as the user is joined to the voting room) were started to send the event stream to each client, which might bring interesting scalability challenges, for which, if you like this topic, we can create a future article about how to place a load balancer to tackle this new challenge
Overall architecture: how it all relates together
The overall strategy was to create a singleton service in ASP.Net that keeps the actor model in memory and all requests that are received from the HTTP endpoints were wrapped into messages that were sent concurrently to the actor model, in a fire-and-forget way, so that these POST requests would always finish right away, i.e. without the client needing to wait for the whole transaction to be done, so this brought asynchronous behavior to the UI/UX which felt natural to the PoC that I was conducting.