Reviewed-on: #31 |
||
---|---|---|
AccessQueuePlayground | ||
AccessQueueService | ||
AccessQueueServiceTests | ||
.gitattributes | ||
.gitignore | ||
AccessQueueService.sln | ||
LICENSE.txt | ||
README.md |
README.md
NOTE: I have added github as a remote but for the latest commits and issue tracking please visit https://git.hobbs.zone/henry/AccessQueueService
AccessQueueService
AccessQueueService is a microservice API designed to control access to a resource with a limited number of concurrent users. It ensures fair access by:
- Granting immediate access if capacity is available.
- Placing users in a queue when the resource is full.
- Automatically managing the queue in a first-in, first-out (FIFO) order.
- Allowing users to revoke their access, freeing up capacity for others.
This service is ideal for scenarios where you need to limit the number of users accessing a resource at the same time, such as online ticket sale platforms that control how many users can purchase tickets concurrently.
Note: This service is not intended to be called directly from end-user client applications, as it could be easily bypassed. Instead, it should be integrated as middleware within your own APIs or backend services.
How the Service Works
-
Requesting Access:
- When a user requests access, the service checks if the current number of active users is below
CapacityLimit
. - If there is capacity, the user is granted access immediately and receives an expiration date set by
ExpirationSeconds
. - If not, the user is added to a queue and receives their position in the queue.
- When a user requests access, the service checks if the current number of active users is below
-
Queueing:
- If a user is placed in the queue, subsequent access requests will return the number of users ahead.
- Users must continually re-request access to remain active in the queue; inactivity may result in losing their spot.
-
Dequeuing:
- Users in the queue are managed in a FIFO (first-in, first-out) order.
- Whenever an access request is made, if there is capacity, the service attempts to dequeue users until capacity is met.
- If a user is dequeued but the time since their last activity is greater than
ActivitySeconds
, they are not granted access and lose their spot in the queue.
-
Maintaining Access:
- Users should continually re-request access while they are active to avoid being considered inactive.
- If
RollingExpiration
is enabled, the expiration is reset whenever access is re-requested.
-
Revoking Access:
- If a user requests access after their expiration date, they must restart the process and re-join the queue if there isn't capacity.
- When a user revokes access (or their access times out), their access expires immediately.
Note on inactivity vs expiration
It is possible for the number of users with access to temporarily exceed the CapacityLimit
if ActivitySeconds
is less than ExpirationSeconds
. This happens because:
- The number of available slots is determined by the time since a user's last activity (
ActivitySeconds
), not by their access expiration (ExpirationSeconds
). - If a user is inactive for longer than
ActivitySeconds
, they no longer count toward the capacity, allowing another user to gain access. - However, the original user still technically has access until their
ExpirationSeconds
elapses.
To ensure the number of users with access never exceeds CapacityLimit
, set ActivitySeconds
equal to ExpirationSeconds
.
API Routes
Request Access
- GET /access/{id}
- Description: Request access for a user with the specified
id
. - Response: Returns an
AccessResponse
object indicating whether access was granted or the user's position in the queue.
- Description: Request access for a user with the specified
Revoke Access
- DELETE /access/{id}
- Description: Revoke access for a user with the specified
id
. This will remove the user from the active list or queue and may allow the next user in the queue to gain access. - Response: Returns a boolean indicating success.
- Description: Revoke access for a user with the specified
Get Service Status
- GET /status
- Description: Returns the current status of the access queue, including active users, queue length, and other statistics.
- Response: Returns an
AccessQueueStatus
object.
Get Configuration
- GET /config
- Description: Returns the current configuration of the access queue service.
- Response: Returns an
AccessQueueConfig
object.
Update Configuration
- POST /config
- Description: Updates the configuration of the access queue service. Accepts a partial or full
AccessQueueConfig
object in the request body. - Response: Returns no content on success (HTTP 204).
- Description: Updates the configuration of the access queue service. Accepts a partial or full
Configuration Variables
Configuration is set in appsettings.json
or via environment variables. The main configuration section is AccessQueue
:
- CapacityLimit: The maximum number of users that can have access at the same time (default: 100).
- ActivitySeconds: How long (in seconds) a user can remain active before being considered inactive (default: 900).
- ExpirationSeconds: How long (in seconds) before an access ticket expires (default: 43200).
- RollingExpiration: If true, the expiration timer resets on activity (default: true).
- CleanupIntervalSeconds: How often (in seconds) the background cleanup runs to remove expired/inactive users (default: 60).
- BackupFilePath: The file path where the access queue state will be periodically saved (no default; optional).
- BackupIntervalSeconds: How often (in seconds) the state is backed up to disk (no default; optional).
Example appsettings.json
:
{
"AccessQueue": {
"CapacityLimit": 100,
"ActivitySeconds": 900,
"ExpirationSeconds": 43200,
"RollingExpiration": true,
"CleanupIntervalSeconds": 60
}
}
State Persistence and Backup
AccessQueueService automatically saves its in-memory state (active users and queue) to disk at regular intervals and restores it on startup. This helps prevent data loss in case of unexpected shutdowns or restarts.
- Backup Location: The backup file path is set via the
AccessQueue:BackupFilePath
configuration variable. If this is not set, no backup will be performed. - Backup Interval: The frequency of backups is controlled by
AccessQueue:BackupIntervalSeconds
(in seconds). If this is not set or is zero, backups are disabled. - Startup Restore: On startup, if a backup file exists at the specified path, the service will attempt to restore the previous state from this file. If the file is missing or corrupted, the service starts with an empty queue and access list.
- Failure Handling: Any errors during backup or restore are logged, but do not prevent the service from running.
- Backup Format: The backup is saved as a JSON file containing the current state of the access queue and active users.
Example configuration:
{
"AccessQueue": {
"BackupFilePath": "Logs/backup.json",
"BackupIntervalSeconds": 60
}
}
Note: Ensure the backup file path is writable by the service. Regular backups help prevent data loss in case of unexpected shutdowns.
AccessResponse Object
The AccessResponse
object returned by the API contains the following properties:
- ExpiresOn (
DateTime?
): The UTC timestamp when the user's access will expire.null
if the user does not have access. - RequestsAhead (
int
): The number of requests ahead of the user in the queue.0
if the user has access. - HasAccess (
bool
): Indicates whether the user currently has access (true ifExpiresOn
is set and in the future).
Running the Service
- Build and run the project using .NET 8.0 or later:
dotnet run --project AccessQueueService/AccessQueueService.csproj
- By default, the API will be available at:
- HTTP: http://localhost:5199
- HTTPS: https://localhost:7291
(See
AccessQueueService/Properties/launchSettings.json
for details.)
Running the Tests
Unit tests for the service are located in the AccessQueueServiceTests
project. To run all tests, use the following command from the root of the repository:
# Run all tests in the solution
dotnet test
Test results will be displayed in the terminal. You can also use Visual Studio's Test Explorer for a graphical interface.
AccessQueuePlayground (Demo UI)
The AccessQueuePlayground
project provides a simple web-based UI for interacting with the AccessQueueService API. This is useful for testing and demonstration purposes.
Running
- Build and run the playground project:
dotnet run --project AccessQueuePlayground/AccessQueuePlayground.csproj
- By default, the playground will be available at:
- HTTP: http://localhost:5108
- HTTPS: https://localhost:7211
(See
AccessQueuePlayground/Properties/launchSettings.json
for details.)
Using
- Open the provided URL in your browser.
- Use the UI to request and revoke access for different user IDs.
- The UI will display your access status, queue position, and expiration time.
This playground is intended only for local development and demonstration.
Configuring
The AccessQueuePlayground
project can be configured via its appsettings.json
file. The main options are:
- RefreshRateMilliseconds: Determines how often (in milliseconds) the playground requests access for all active users and updates the UI. Lower values provide more real-time updates but may increase load on the service.
- ServiceUrl: The URL of the AccessQueueService API to use. If this is set, all API requests from the playground will be sent to the specified service URL (e.g.,
https://localhost:7291/
).- If
ServiceUrl
is not provided, the playground will use an internal instance of AccessQueueService, configured using the playground's ownappsettings.json
values under theAccessQueue
section. This is useful for local development and testing without running the service separately.
- If
Example configuration in AccessQueuePlayground/appsettings.json
:
{
"AccessQueuePlayground": {
"RefreshRateMilliseconds": 200,
"ServiceUrl": "https://localhost:7291/"
},
"AccessQueue": {
"CapacityLimit": 3,
"ActivitySeconds": 2,
"ExpirationSeconds": 10,
"RollingExpiration": true
}
}
Tip: Adjust
RefreshRateMilliseconds
for your use case. For most demos, 100–500ms works well. If you are connecting to a remote or production AccessQueueService, consider increasing the refresh interval (e.g., 1000ms or higher) to account for network latency and reduce unnecessary load.
License
See LICENSE.txt for license information.