A modern, full-stack recreation of a classic MMORPG, built with a microservice architecture and designed for scalability, maintainability, and a rich developer experience.
Hagalaz means "hail" in Proto-Germanic and represents natural disruption and transformation. Symbolizing the uncontrollable forces of nature, it signifies unexpected challenges that lead to growth and renewal.
This repository is a comprehensive application built using modern development technologies and best practices. It leverages a powerful stack to deliver a robust and scalable platform.
- Backend: .NET 10, C#, ASP.NET Core
- Frontend: Angular, Electron
- Orchestration: .NET Aspire
- Infrastructure: Docker, MySQL, RabbitMQ, Redis
- Data access: Oracle
MySql.EntityFrameworkCore10 with EF Core 10; local and integration databases use MySQL 8.4. - Communication: REST APIs, gRPC, WebSockets
- Messaging: MassTransit for reliable, asynchronous communication.
- Authentication: OpenIddict implementing OAuth2 and OpenID Connect.
- Resilience: Polly for transient-fault handling and resilience patterns.
- Caching: FusionCache with Redis for high-performance data caching.
- Observability: OpenTelemetry for standardized logs, metrics, and traces.
- API Standards: OpenAPI for documentation, tested with Scalar.
The solution is organized into a microservice architecture, with clear separation of concerns between projects.
-
Hagalaz.AppHost: The .NET Aspire orchestration project. This is the entry point for running the application locally. It defines all the services, databases, and other resources, and manages their configuration and lifecycle. -
Hagalaz.ApiService: The public-facing API gateway. It acts as a reverse proxy (YARP) and the primary entry point for the Angular client, routing requests to the appropriate backend services and handling cross-cutting concerns like authentication. -
Services/: This directory contains the individual microservices that make up the application's backend logic.Hagalaz.Services.GameWorld: Manages core gameplay logic, character state, and interactions within the game world.Hagalaz.Services.Login: Handles the player login and character selection process.Hagalaz.Services.Store: (Example) Manages in-game shops or other transactional features.- (Other services follow this pattern)
-
Libraries/: Contains shared libraries and abstractions used across multiple services to reduce code duplication and enforce consistency.Hagalaz.Game.Abstractions: The foundational project for the game's domain model. It defines the core interfaces (ICharacter,IItem), enums, and data structures that represent all entities and concepts within the game world.Hagalaz.Cache: A dedicated library for reading and parsing the game's data cache files.Hagalaz.Network.Common: Provides common networking utilities, including packet composition and read/write operations.Hagalaz.Security: Implements security-related functionalities like data encryption and hashing.
-
Tests/: Contains all unit, integration, and end-to-end tests for the solution, ensuring code quality and reliability.
Before you begin, ensure you have the following installed:
- .NET 10 SDK
- .NET Aspire Workload
- Docker Desktop
- A compatible game client and its corresponding data cache. The cache files should be placed in a
/Cachedirectory at the root of the solution.
The easiest way to get the entire application running is by using the .NET Aspire AppHost project.
-
Clone the repository:
git clone https://github.com/your-username/hagalaz.git cd hagalaz -
Run the AppHost:
dotnet run --project Hagalaz.AppHost/Hagalaz.AppHost.csproj
-
Launch the Aspire Dashboard: Once the project is running, .NET Aspire will start all the configured services, databases, and containers. You can view the status, logs, and traces of all resources in the Aspire Dashboard, which typically launches automatically in your web browser.
Most of the service discovery and configuration is handled automatically by .NET Aspire. However, service-specific settings can be found and modified in the appsettings.json file of each individual service project (e.g., Hagalaz.Services.GameWorld/appsettings.json).
Database schema changes are owned by Hagalaz.Database.Migrations, a one-shot executable.
In local Aspire development it waits for MySQL, applies all pending migrations, and the
database-dependent services wait for its successful completion before starting.
For a production rollout, publish and run the migrator once as a deployment step or
Kubernetes Job, then start or update the application services only after it exits with code
0:
dotnet publish Hagalaz.Database.Migrations/Hagalaz.Database.Migrations.csproj -c Release -o ./publish/migrations
ConnectionStrings__hagalaz-db="Server=...;Database=hagalaz-db;User=...;Password=..." \
dotnet ./publish/migrations/Hagalaz.Database.Migrations.dllDo not run a migration init container on every application replica; that recreates the multiple-migrator startup race. The migrator retains the MySQL advisory lock as defense in depth for accidental duplicate execution.
The EF tool is pinned in .config/dotnet-tools.json and uses Oracle's MySQL provider. Supply a design-time connection with --connection=<value> (or ConnectionStrings__hagalaz-db) when running these commands:
dotnet tool restore
dotnet ef migrations list --project Hagalaz.Data/Hagalaz.Data.csproj -- --connection="Server=localhost;Database=hagalaz-db;User=root;Password=..."
dotnet ef migrations script --project Hagalaz.Data/Hagalaz.Data.csproj -- --connection="Server=localhost;Database=hagalaz-db;User=root;Password=..."
dotnet ef database update --project Hagalaz.Data/Hagalaz.Data.csproj -- --connection="Server=localhost;Database=hagalaz-db;User=root;Password=..."
dotnet ef migrations has-pending-model-changes --project Hagalaz.Data/Hagalaz.Data.csproj -- --connection="Server=localhost;Database=hagalaz-db;User=root;Password=..."A typical workflow for adding a new feature might look like this:
- Define Contracts: Add or update interfaces and models in
Hagalaz.Game.Abstractions. - Implement Service Logic: Implement the new business logic within the relevant microservice in the
Services/directory. - Add API Endpoints: Expose the new functionality via an API endpoint in the service.
- Write Tests: Add unit and integration tests for the new logic in the corresponding
Tests/project. - Consume in Client: Update the Angular frontend to consume the new endpoint.
Contributions are welcome! Please fork the repository, create a new branch for your feature or fix, and submit a pull request.
This project is licensed under the GNU General Public License v3.0. See the LICENSE file for details.
- Author: Frank
- GitHub: frankvdb7