Skip to content

Architecture

Before diving into the details of the architecture here is a quick overview of the current architecture. This drawing omits some details of the actual architecture but is still correct for the most part.

Overview

The project is based on a Hexagonal architecture, this allows for the the GitHub and Prometheus specific code to live in its own adapters and exporters making the service easily extensible. Those two adapter and exporter implementations are built around a domain module.

This also means that anything specific to the implementation of the adapter or the exporter is only present in each respective module and does not need to be considered from other modules. Examples would be that caching requests is done in the github-adapter and not in the domain.

Domain

The domain exposes different use cases for getting the data from the adapter implementation. This data, although modeled after the Github apis structure is not completly coupled to it and is already more generic then Github's. Fetching the data happens through implementations of the respective ...QueryPort injected by spring.

Since much of the data depends on eachother, like Workflow Runs are part of a Repository, the use cases call eachother to fetch the nessesary data.

Prometheus Exporter

Through the Micrometer dependency the prometheus exporter exposes its metrics on the /actuator/prometheus endpoint. Internally the exporter calls the domain's use cases transforming the returned data into a format that is specific to each Exporter and then exposing it.

Each exporter runs on a scheduled interval to fetch new data. This interval can be very frequent since the exporter should not be concerned about source details like ratelimiting. The interval is nontheless configurable, you can find the possible configurations here

Github Adapter

As mentioned in the domain section of this page, the adapter implementation should implement all QueryPort's with which the data gets fetched. Our implementation uses springs RestClient to do this making all the nessesary requests to fetch the data.

Mapping to Domain

This topic is explored more in the Api to Domain Mapping page but I thought I would mention a few things here. Each request response gets gets mapped to a github-adapter specific class which then knows how to turn itself into the relevant domain class. This allows the domain structure to be somewhat decoupled from Githubs internal structure and easy extension to other Github like Api's. For more details on this mapping go to the above mentioned page

Request Caching

To avoid slamming the Api with thousands of requests the adapter uses springs @Cachable annotation to cache function calls. This is especially important becuase a lot of use cases need the same data which would lead to a lot of unnessesary requests.

Example

The list of repositories is needed by almost all use cases. The number of workflow runs or the number of self hosted runners is repository specific, for this reason all these use cases will make a call to the RepositoriesQueryPort and fetch the nessesary data. But after the first call this result actually almost never changes. Here a cached result comes in handy since it avoids the high number of actual requests to the Api.