Master's thesisFRI UNIZA

A modular web system for the Department of Informatics

A microservices platform that brings independently developed modules together into a single department portal - from architecture and integration through automated deployment to monitoring in production.

uat.ki.fri.uniza.sk
Home screen of the Department of Informatics portal
Module
Identity Service running
Trace
POST /login · 142ms
trace_id: a3f8b2…
Event Bus
UserRegistered
{ "userId": "a83…" }

Why a modular web system?

For years, the existing Department of Informatics portal was little more than a static business card - no sign-in, no content management, no way to extend it. The goal was not just a visual refresh, but to build a platform that can be extended with new modules without touching its core.

Modularity

New functionality is built as a separate module. Each module is independent - adding or changing one does not affect the other modules or the system core.

Central sign-in

The system core handles sign-in with both university Microsoft Entra ID accounts and local invitation-based accounts. Modules don't deal with authentication or roles themselves, they simply use them.

Continuity

The project originated in project-based courses and serves as a foundation on which students can build their own extension modules in future semesters.

Thesis goals

The thesis covers two areas: integration deals with how modules work together, and orchestration with how they get to production automatically.

Module integration

How modules work together

Designing a microservices architecture and implementing the key modules so that they are technologically independent yet cooperate reliably - with shared sign-in, data and events.

  • microservices
  • Microsoft Entra ID
  • RabbitMQ
  • gRPC
  • API gateway
View the architecture
Module orchestration

How modules get to production

Containerization and CI/CD pipelines that automate builds, tests, delivery to production environments and real-time system monitoring.

  • Docker
  • GitHub Actions
  • OpenTelemetry
  • Grafana
  • Teams alerting
View the orchestration

Key parts of the system

The thesis implements three modules - user management, department portal content and study materials. They are complemented by a shared service that brings semantic search across the whole system.

Identity

Single sign-in and user management

Sign-in with university accounts (Microsoft Entra ID) as well as local invitation-based accounts. Management of users and their global and module-specific roles, including public profiles.

Portal

Department portal content

News with a WYSIWYG editor and search support, a list of department members synchronized from LDAP, courses and research projects.

Courses

Study materials

Study materials for courses organized into chapters. Demonstrates how new extension modules are added to the system.

Shared service

Semantic search across modules

Users don't need to know the exact words - they just describe what they're looking for and the system finds content matching the meaning of their query. Behind the scenes, the service creates vector representations of content (embeddings) using a local ONNX model and stores them in a Qdrant DB. Both the portal and study materials use it this way.

Text
Embedding
Qdrant
Results

System architecture

A look at the individual components and their relationships within the system

keep scrolling ↓
01 · OVERVIEW

The whole modular system

The modular system consists of a web client, four services, their own databases and infrastructure for messaging and observability. The services are deployed as containers via Docker Compose.

02 · WEB CLIENT

Web application

A unified user interface built with Angular. It communicates with all modules through an Nginx API gateway that routes requests to the appropriate services - so the client acts as a single portal.

03 · IDENTITY MODULE

Central sign-in

IdentityService issues JWT tokens signed with RSA asymmetric cryptography, manages global and module roles and publishes user events via the Event Bus. It connects the system to Microsoft Entra ID and the FRI LDAP.

04 · PORTAL MODULE

Department portal content

PortalService manages news, department members, courses and research projects. Via the Event Bus it maintains a shadow table of users and synchronizes department members from the FRI LDAP.

05 · COURSES MODULE

Study materials

An extension module for study materials organized into chapters. It demonstrates how new modules are added to the system - without touching the core or the other modules.

06 · SEMANTIC SEARCH

Vector Indexer Service

A universal service for generating embeddings and semantic search - it uses a local ONNX model and the Qdrant vector database. Both Portal and Courses call it synchronously over gRPC when searching news and study materials.

07 · INTERNAL TOOLS

Messaging and observability

The RabbitMQ Management UI and the Grafana observability stack (Grafana + Loki + Tempo). Used by administrators for monitoring and by developers for diagnostics - accessible only via the UNIZA VPN.

Custom NuGet libraries

Two custom libraries used across the system's .NET services.
Published via GitHub Packages.

MIS.Mediator

in-process

A custom implementation of the mediator pattern - it separates the API layer from business logic via IMediator.SendAsync(). Created as a replacement for MediatR after its license change.

// Controller - IMediator via DI (primary ctor)
public class ArticlesController(IMediator mediator)
    : ControllerBase
{
    [HttpGet("{slug}")]
    public async Task<IActionResult> Get(string slug)
    {
        var article = await mediator.SendAsync(
            new GetArticleQuery(slug));
        return Ok(article);
    }
}
// Request + handler (.Business project)
public record GetArticleQuery(string Slug)
    : IRequest<ArticleDto>;

public class GetArticleHandler(AppDbContext db)
    : IRequestHandler<GetArticleQuery, ArticleDto>
{
    public Task<ArticleDto> HandleAsync(
        GetArticleQuery q, CancellationToken ct)
    {
        // load the article + map it to ArticleDto
    }
}
// Program.cs
// AddMediator discovers and registers all
// IRequest + IRequestHandler in the given assembly
builder.Services.AddMediator(
    typeof(GetArticleQuery).Assembly);

MIS.EventBus

cross-service

An abstraction over RabbitMQ - one service publishes events, others consume them, while the library takes care of the message broker communication details. Services thus communicate with each other asynchronously.

// IdentityService - IEventBus via DI
public class AuthService(IEventBus eventBus)
{
    public async Task Register(UserProfile user)
    {
        // ... save the user
        await eventBus.Publish(
            new UserRegisteredEvent(user));
    }
}
// Event + consumer (PortalService)
[Exchange(prefix: "IdentityService")]
public record UserRegisteredEvent(UserProfile User);

public class UserRegisteredConsumer(AppDbContext db)
    : IConsumer<UserRegisteredEvent>
{
    public Task Consume(UserRegisteredEvent msg)
    {
        // synchronize the shadow table
    }
}
// Program.cs
// AddEventBus connects RabbitMQ and auto-registers
// all IConsumer<T> in the given assembly
builder.Services.AddEventBus(
    typeof(UserRegisteredConsumer).Assembly);

From integration to orchestration

Once developed, the system needs to be reliably delivered to the production environment for end users, and its operation then monitored so that any errors are caught early. The second area of the thesis automates these phases - from code changes all the way to error notifications.

Source code in a GitHub organization

The system is split into repositories by module, gathered in a single private GitHub organization - giving the whole team one place with an overview of all parts of the system. Each repository has its own CI/CD pipelines, sensitive data is kept out of the code in GitHub Secrets, and the custom libraries are published to a private NuGet feed via GitHub Packages.

mis-platform

Core

WebClient, IdentityService, PortalService and infrastructure - RabbitMQ and the Grafana stack. The core services are deployed together.

mis-courses-module

Module

CoursesService - the study materials module. Demonstrates how the system is extended with an independently deployable module.

mis-vector-indexer-service

Service

VectorIndexerService - an internal gRPC service for semantic search (embeddings via ONNX + Qdrant). It doesn't belong to any module.

mis-mediator-lib

Library

NuGet package MIS.Mediator - an in-process CQRS mediator. Published to GitHub Packages.

mis-eventbus-lib

Library

NuGet package MIS.EventBus - an abstraction over RabbitMQ. Published to GitHub Packages.

mis-sample-module

Template

A skeleton for building new modules - ready-made structure, configuration and pipelines to get started.

mis-docs

Documentation

Internal technical documentation for the whole development team - architecture, onboarding of new members, conventions and module integration guidelines.

replace-tokens-action

GitHub Action

A custom GitHub Action - during deployment it replaces placeholders in .env with real values from Secrets.

Automated CI/CD pipelines

Four types of GitHub Actions workflows automate change validation, production deployments, database migrations and publishing new versions of the custom libraries.

PR Validation

Automatic on PR
  • Validation build
  • Unit tests (xUnit)
  • Integration tests (Testcontainers)
  • Results reported back to the Pull Request

Release to Production

Manual trigger
  • Docker image build tagged with the version
  • Secrets substitution and deployment
  • Verification via Health Check
  • Automatic GitHub Release

Migrate DB Production

Manual trigger
  • Image build with an EF Bundle
  • Applying schema migrations to the production DB
  • Migration rollback support

Publish NuGet Package

Manual trigger
  • Build & publish .nupkg
  • Push to GitHub Packages
  • Automatic GitHub Release
Main pipeline

Release to Production - a chain of four jobs

A manual trigger from the GitHub UI kicks off four consecutive jobs. Both build (ci) and deploy (cd) are extracted into reusable workflows, so they can also be run on their own.

1 version
  • Computes the version vYY.MM.DD.N
  • from the date + the sequence of that day's releases
2 ci · build reusable
  • Build secrets substitution
  • docker compose build
  • Tagging images with the version
3 cd · deploy reusable
  • Secrets substitution into .env (replace-tokens-action)
  • Copying compose & .env to the server
  • docker compose up
  • Health Check /health
4 release
  • GitHub Release with the version tag
  • Auto-generated notes (PRs since the last release)

Runs on a self-hosted runner - build and deploy on the same server.

Monitoring with the Grafana observability stack

Every service sends logs and distributed traces via OpenTelemetry to a central monitoring system. The trace detail shows which services a request passed through. Thanks to Trace ID correlation, only the logs related to a given trace are shown.

Grafana Loki Tempo OpenTelemetry
Trace · sign-in via Microsoft Entra ID trace_id: a3f8b2…
IdentityService · POST login.microsoftonline.com 54 ms
Identity DB · user verification 12 ms
Identity DB · loading the LDAP copy 11 ms
Identity DB · inserting the user 13 ms
Event Bus · publishing UserRegisteredEvent 7 ms
PortalService · shadow table sync 18 ms
CoursesService · shadow table sync 16 ms
Identity DB · storing the Refresh Token 13 ms
IdentityService Identity DB Event Bus PortalService CoursesService

One request, one Trace ID - across all services.

In the Grafana dashboard, just click a request in the trace list to see its span timeline along with all related logs correlated by Trace ID. This makes finding the root causes of errors in a distributed system much easier.

  • Trace list from all services
  • Span timeline of the selected trace
  • Logs correlated by Trace ID

Real-time alerting via Microsoft Teams

When an error is logged, Grafana automatically notifies the administrators. Via a Power Automate webhook, a notification arrives in Teams with a list of errors and a button linking straight to the trace in Grafana.

  1. Error log a service logs an error (log level error)
  2. Grafana alert rule catches the error log and sends it as an Adaptive Card (JSON) to Power Automate
  3. Power Automate webhook the flow publishes the received message to Teams
  4. Teams Group Chat real-time notification
Error in production
IdentityService14:32:08
Unhandled exception: LDAP connection timeout
PortalService14:32:09
Failed to consume UserRegisteredEvent
Open trace in Grafana

System showcase

Selected features of the deployed system, grouped by module.
The screenshots capture the system as it was at the time of the thesis defense.

Dominik Ježík Master's thesis - Modular Web System

Ing. Dominik Ježík

Software Engineer

I'm a graduate of the Faculty of Management Science and Informatics at the University of Žilina, where I defended my master's thesis Modular Web System - Integration and Orchestration of Modules.

It gave me valuable experience not only with development, but above all with system design and architectural decisions at the level of an entire system - from designing a microservices architecture through automated deployment to monitoring and alerting in production. Looking at software as a whole is what I enjoy and what keeps pushing me forward.

Earlier, I also defended my bachelor's thesis The Web Part of the First Responder System.