1. Introduction

CryptoMonitor is a microservice-base web application designed to effectively monitor and manage cryptocurrencies. It allows users to monitor live market trends, maintain individual portfolios, and receive customizable notifications for price fluctuations. With an interactive and intuitive interface, CryptoMonitor provides interactive charts, historical performance reports, and asset allocation information. Registered users can create a personalized watchlist, set up push notifications, and monitor investments in different currencies. Even without an account, one is welcome to browse live market data and trends. CryptoMonitor offers the tools to stay informed and make decisions based on knowledge, whether you are a professional trader or beginner.

2. Domain Driven Design

The first step in the development of the system has been to understand the domain in which the application will operate. The Domain name identified is CryptoMonitor, composed of the two words Crypto, so Cryptocurrencies and Monitor that stands for the verb Monitoring. So, the domain is about Monitoring Cryptocurrencies.

2.1. Ubiquitous Language

Once identified the domain name, the next step has been to define the Ubiquitous Language used by the team. In this phase, the key concepts related to the domain under analysis are identified. Subsequently, a glossary of terms and their definitions is created, establishing a shared language for all project stakeholders.

Term Definition

System

The application that gives access and functionality to not-logged users and logged users.

Application

A tool accessible via the web.

Not-Logged User

A user who didn’t register to the system.

Logged User

A user registered to the system that can access additional functionality, like having a personal wallet and a watchlist.

User

A person that uses the application.

Account

A set of data related to a user.

Authentication

A process where a not-logged user can become a logged user using their credentials.

Credentials

A pair of email and password that identify an account.

Wallet

A list of crypto assets that the logged user owns.

WatchList

A list of cryptos that the logged user decided to observe.

Crypto Asset

A set of data (buying price/date, selling price/date, quantity) related to a crypto that the user owns.

Crypto Details

A set of data related to a crypto (name, symbol, price, market cap, 24h volume, price variation).

Notification

A message from the system sent to a logged user related to a crypto in their watchlist.

Crypto

A cryptocurrency.

Report

An analysis of the wallet of a logged user during a timespan.

Performance

The overall value trend of the wallet based on historical crypto prices, considering any purchase or sale operations.

Interactive Chart

A visual representation that allows users to analyze historical cryptocurrency or wallet data with interactive features such as zoom, time filters, and customizable views.

FIAT Currency

A traditional currency used to compare the market value of cryptocurrencies (EUR, USD, etc.).

Price Variation

Fluctuation in a crypto’s value.

24h Volume

The total trading value of a cryptocurrency over the last 24 hours.

Market Cap

The total value of a cryptocurrency, calculated by multiplying its current price by the total circulating supply.

Transaction

A movement of a crypto related to the wallet. It can be a purchase or a sale and is used to track the asset.

Crypto Market

A set of the most important cryptocurrencies.

Price Limits

A threshold on the price of a crypto.

2.2. Requirements Analysis

Here are listed the requirements identified during the analysis phase. They are divided into system, business and functional requirements. To enforce the analysis, the requirements are also linked to the use cases and user stories, providing also a detail description of them.

2.2.1. System Requirements

  1. The system has to provide login, registration, and logout in a safe way.

  2. The system has to provide a page to see the info related to the account.

  3. The system has to allow the user to edit the info related to the account.

  4. The system has to allow the user to monitor the crypto market.

  5. The system has to allow the user to see a detail for each crypto.

  6. The system has to allow the logged user to access his wallet and see reports of its historic performance.

  7. The system has to allow the logged user to add and remove transactions in the wallet.

  8. The system has to allow the logged user to access his watchlist.

  9. The system has to allow the logged user to add or remove cryptos to his watchlist.

  10. The system has to allow the logged user to set price limits on the cryptos present in the watchlist that can trigger notifications when they’re crossed.

  11. The system has to allow all the users to visualize the crypto’s prices in different FIAT values.

2.2.2. Business Requirements

  1. The system must allow all users to monitor the cryptocurrency market in real-time.

  2. The system must allow users to access detailed information about each cryptocurrency.

  3. The system must provide logged-in users with tools to manage a personal wallet and analyze its historical performance.

  4. The system must notify logged-in users of significant price changes or configured limits.

2.2.3. Functional Requirements

  1. The system must provide secure registration, login and logout functionalities.

  2. The system must provide a page to visualise the account.

  3. The system must allow users to edit the information related to the account.

  4. The system must display cryptocurrency prices in different FIAT currencies (e.g., EUR, USD).

  5. The system must allow users to view details about cryptocurrencies, including:

    • The symbol and name of the cryptocurrency.

    • The price in FIAT currencies.

    • Price variations over a specified timespan.

  6. The system must allow users to filter and sort the crypto market.

  7. The system must allow logged-in users to:

    • Access their wallet.

    • Add and remove transactions in the wallet.

    • View reports of the wallet’s historical performance.

  8. The system must allow logged-in users to:

    • Access their watchlist

    • Add and remove cryptocurrencies from their watchlist.

    • Configure price limits on cryptocurrencies in the watchlist.

  9. The system must generate notifications when configured price limits are crossed.

2.2.4. Use Cases and User stories: Authentication

usecaseauth
Figure 1. Use Case Diagram: Authentication
  • US1: Registration

    As a not-logged user,
    I want to register an account
    so that I can access the system's features.

Actors

Not logged user

Precondition

User is not registered to the system

Postcondition

User is registered to the system

Trigger

User clicks on the sign-up button

Acceptance Criteria

- The user must be able to access the registration page.

- The registration form must validate inputs (e.g., email format, password strength).

- The system should display an error message for invalid or duplicate emails.

- On successful registration, the user is redirected to the login page.

Exceptions

- Invalid email format.

- Weak password.

- Duplicate email.

  • US2: Login

    As a not-logged user
    I want to authenticate into an account
    so that i can access to my private data

Actors

Not logged user

Precondition

User is not logged into the system

Postcondition

User is logged into the system

Trigger

User clicks on the sign-in button

Acceptance Criteria

- The user must be able to access the login page.

- The system should display an error message for invalid credentials.

- On successful authentication, the user is redirected to the account page.

Exceptions

- Invalid credentials.

  • US3: Logout

    As a logged user
    I want to log out of my account
    so that i can securely end my session.

Actors

Logged user

Precondition

User is logged into the system

Postcondition

User is not logged into the system

Trigger

User clicks on the logout button

Acceptance Criteria

- The user can log out from the account page.

- After logout, the user is redirected to the homepage.

2.2.5. Use Cases and User stories: Generic

usecasegeneric
Figure 2. Use Case Diagram: Generic
  • US4: View Account

    As a logged user
    I want to see details of my account
    so that i can see my information and logout.

Actors

Logged user

Precondition

User is logged into the system

Postcondition

User can see details of his account

Trigger

User clicks on the account detail button

Acceptance Criteria

- The user can see his info on the account page.

- If the system is not working, an error message is shown.

Exceptions

- The system is down.

  • US5: Edit Account

    As a logged user
    I want to edit my account information
    so that I can update my personal data.

Actors

Logged user

Precondition

User is on the account page

Postcondition

User account information is updated

Trigger

User clicks on the edit account button

Acceptance Criteria

- The system displays the form to edit the account’s fields.

- The user edits his account’s fields.

- If the system is not working, an error message is shown.

- If the edit is not successful, an error message is shown.

Exceptions

- The system is down.

  • US6: Monitor Crypto Market

    As a user (both logged and not logged),
    I want to access the homepage,
    So that I can monitor the cryptocurrency market.

Actors

Logged user, Not logged user

Precondition

User can access the system

Postcondition

User can access the homepage

Trigger

The user presses the home button or accesses the system

Acceptance Criteria

- The homepage displays information about cryptocurrencies.

- If the system is not working, an error message is shown.

Exceptions

- The system is down.

Technical Details

- Display the symbol, name, and price of each cryptocurrency.

- Show prices in FIAT currency.

- Include price variation within a specific timespan (24h volume), the market cap.

- Allow filtering and sorting on the market.

  • US7: Watch a Crypto

    As a user (both logged and not logged),
    I want to access the crypto details,
    So that I can monitor the specific crypto.

Actors

Logged user, Not logged user

Precondition

User can access the system

Postcondition

User can access the detail page

Trigger

User clicks on a crypto in the list

Acceptance Criteria

- If the system is down, an error message should indicate the issue.

- The page displays information about the selected crypto.

Exceptions

- API issue

Technical Details

- Display the symbol, name of each cryptocurrency and the chart.

- Show prices in FIAT currency.

- Include Circulating Supply, Max Supply, All Time High, All Time High Change, Useful Links, and Community Sentiment.

  • US8: Access Wallet

    As a logged user,
    I want to access my wallet,
    So that I can monitor my wallet history and value.

Actors

Logged user

Precondition

User is logged into the system

Postcondition

User can access the wallet page

Trigger

User clicks on the wallet button

Acceptance Criteria

- The page displays information about the wallet.

- If the system is down, an error message should indicate the issue.

Exceptions

- API issue.

Actors

Logged user

Precondition

The user is in the wallet page

Postcondition

The wallet performance report is displayed

Trigger

User clicks on the report button

Acceptance Criteria

- The page displays information about the wallet performance.

- If the system is down, an error message should indicate the issue.

Exceptions

- API issue.

  • US9: Manage Transaction

    As a logged user,
    I want to add/remove transactions into my wallet,
    So that I can keep track of my owned crypto and update the wallet history.

Actors

Logged user

Precondition

User is in the wallet page

Postcondition

The transaction is saved in the system

Trigger

User clicks on the save transaction button

Acceptance Criteria

- The user can see if the transaction succeeded.

- If the system is down, an error message should indicate the issue.

Exceptions

- The system is down.

Actors

Logged user

Precondition

User is in the wallet page and the transaction is shown in the wallet

Postcondition

The transaction is removed from the system

Trigger

User clicks on the remove transaction button

Acceptance Criteria

- The user can see if the transaction removal succeeded.

- If the system is down, an error message should indicate the issue.

Exceptions

- The system is down.

  • US10: Access Watchlist

    As a logged user,
    I want to access to my watchlist,
    So that I can manage my preferred cryptos.

Actors

Logged user

Precondition

User is logged into the system

Postcondition

User can access the watchlist page

Trigger

User clicks on the watchlist button

Acceptance Criteria

- The page displays all the crypto added to the watchlist.

- If the system is down, an error message should indicate the issue.

Exceptions

- API issue.

  • US11: Add Crypto to Watchlist

    As a logged user,
    I want to add/remove cryptos to my watchlist,
    So that I can monitor the preferred cryptos.

Actors

Logged user

Precondition

User is on the crypto detail page

Postcondition

The crypto is added to the watchlist

Trigger

User clicks on the “add to watchlist” button

Acceptance Criteria

- The user knows if the add was successful.

- The crypto is shown in the watchlist page.

- If the system is down, an error message should indicate the issue.

Exceptions

- API issue.

  • US12: Remove Crypto from Watchlist

    As a logged user,
    I want to remove cryptos from my watchlist,
    So that I can manage the preferred cryptos.

Actors

Logged user

Precondition

User is on the watchlist page and the crypto is in the watchlist

Postcondition

The crypto is removed from the watchlist

Trigger

User clicks on the “Delete” button on the crypto row

Acceptance Criteria

- The user knows if the removal was successful.

- The crypto is not shown anymore in the watchlist page.

- If the system is down, an error message should indicate the issue.

Exceptions

- API issue.

2.2.6. Use Cases and User stories: Notification

usecasenotification
Figure 3. Use Case Diagram: Notification
  • US13: Create/Delete notification from watchlist

    As a logged user
    I want to create/delete notification from my watchlist,
    So that I can keep track of the price of my watched cryptos

Actors

Logged user, Notification system

Precondition

User is in the watchlist page

Postcondition

The notification is created for the crypto

Trigger

User clicks on the “enable notification” button and completes the form

Acceptance Criteria

- The notification system starts monitoring the price.

- If an error occurs, the system notifies the user.

Exceptions

- API issue

Technical Details

- The user can add a threshold price for a crypto.

Actors

Logged user, Notification System

Precondition

User is in the watchlist page, and the user goes to the notification list

Postcondition

The notification is deleted for the crypto

Trigger

User clicks on the “delete notification” button on a notification

Acceptance Criteria

- The notification system stops monitoring the crypto.

- If an error occurs, the system notifies the user.

Exceptions

- API issue

  • US14: Enable/Disable notification

    As a logged user
    I want to enable/disable notification from my watchlist,
    So that I can manage the notifications of my watched cryptos

Actors

Logged user, Notification system

Precondition

User is in the watchlist page

Postcondition

The notification is enabled for the crypto

Trigger

User uses the active switch and sets it to active

Acceptance Criteria

- The notification system starts monitoring the price.

- If an error occurs, the system notifies the user.

Exceptions

- API issue

Actors

Logged user, Notification system

Precondition

User is in the watchlist page

Postcondition

The notification is disabled for the crypto

Trigger

User uses the active switch and sets it to inactive

Acceptance Criteria

- The notification system stops monitoring the price.

- If an error occurs, the system notifies the user.

Exceptions

- API issue

  • US15: Receive notification

    As a logged user
    I want to receive notifications from the system,
    So that I can be informed about the price of my watched cryptos

Actors

Notification System, Logged User

Precondition

Notification is active for a crypto

Postcondition

The logged user is notified

Trigger

The monitored crypto reaches the price limit

Acceptance Criteria

- A notification is sent to the user.

Exceptions

- API issue

  • US16: Monitor Crypto Price

    As a logged user
    I want to monitor the price of a crypto,
    So that I can be informed about the price of my watched cryptos

Actors

Notification System, Logged user

Precondition

Notification is active for a crypto

Postcondition

The Notification System monitors the price of the crypto

Trigger

User sets a price limit for a crypto

Acceptance Criteria

- The Notification System monitors the price of the crypto

Exceptions

- API issue

2.3. Event Storming

The knowledge crunching session was conducted using the Event Storming approach, that facilitates collaboration between developers and domain experts to explore and define the project’s domain. By utilizing color-coded sticky notes, the team iteratively identifies key elements, including actors, domain events, commands and views/read models, ensuring a comprehensive understanding of the system’s behavior and requirements. The steps followed during the Event Storming session are described as follows, until to the production of the context map.

2.3.1. Step 1: Identify Domain Events

Here are the domain events that can happen in the system identified during the Event Storming session:

Domain Events

2.3.2. Step 2: Timeline

The timeline is created to visualize the sequence of events and the flow of information in the system:

Timeline

2.3.3. Step 3: User Actions, External systems and Business Process

The user actions (Commands, Actors, Read Model), external systems, and business processes are identified to understand the system’s interactions:

User Actions

2.3.4. Step 4: Aggregates

The aggregates are identified to group the domain events and commands that are related to the same entity:

Aggregates

2.4. Bounded Contexts

After the Event Storming session, the team identified the Bounded Contexts that will be part of the system. Each Bounded Context is a boundary within which a domain model is defined and applicable. For each of them, the team linked the relative ubiquitous language, commands and domain events. The Bounded Contexts identified are:

2.4.1. Crypto Market Context

It is the source for all cryptocurrency-related data. It manages information about cryptocurrencies, including their IDs, symbols, current prices, and historical price data. Additionally, it can handle currency conversions between different fiat and crypto currencies, ensuring that all other contexts rely on consistent and up-to-date market information.

Ubiquitous Language

Term Definition

Crypto

A cryptocurrency.

Crypto Market

A set of the most important cryptocurrencies.

Price Variation

Fluctuation in a crypto’s value.

24h Volume

The total trading value of a cryptocurrency over the last 24 hours.

Market Cap

The total value of a cryptocurrency, calculated by multiplying its current price by the total circulating supply.

FIAT Currency

A traditional currency used to compare the market value of cryptocurrencies (EUR, USD).

Price Limits

A threshold on the price of a crypto.

Crypto Details

A set of data related to a crypto (name, symbol, price, market cap, 24h volume, price variation).

Aggregate Root:

  • Crypto: Represents a cryptocurrency with its details and forms an aggregate root with the value object Price. It has the following attributes:

    • cryptoID: Unique identifier for the crypto.

    • Name: The name of the crypto.

    • Symbol: The symbol of the crypto.

    • currentValue: The current value of the crypto.

Value Objects:

  • Price: Represents the price of a crypto. It has the following attributes:

    • value: The value of the crypto.

    • currency: The currency in which the value is expressed.

Repositories:

  • CryptoRepository: Manages the persistence and retrieval of crypto data.

Services:

  • MarketDataService: Provides functionalities to fetch and update market data.

Commands:

  • selectCryptoCommand: Select a crypto to see its details.

  • filterCryptoCommand: Filter the crypto market based on specific criteria.

  • sortCryptoCommand: Sort the crypto market based on specific criteria.

Domain Events:

  • MarketDataRefreshedEvent: The crypto market data has been updated.

2.4.2. Watchlist Context

Enables users to manage a personalized list of favorite or tracked cryptocurrencies. Users can add or remove cryptos from their watchlist. This context allows users to monitor specific cryptocurrencies of interest without handling the complexity of market data.

Ubiquitous Language

Term Definition

WatchList

A list of cryptos that the logged user decided to observe.

Crypto

A cryptocurrency.

Price Variation

Fluctuation in a crypto’s value.

Entities:

  • WatchlistItem: Represent a single item in the watchlist. It contains the following attributes:

    • itemID: The unique identifier for the item.

    • cryptoID: The unique identifier for the crypto.

    • addedAt: The date and time when the crypto was added to the watchlist.

Aggregate Root:

  • Watchlist: Represents the user’s watchlist of cryptocurrencies. It is an aggregate root that contains a list of Crypto. It has the following attributes:

    • watchlistID: The unique identifier for the watchlist.

*Services:

  • WatchlistService: Provides functionalities to manage the watchlist, such as adding or removing cryptos.

Commands:

  • addCryptoToWatchlistCommand: Add a crypto to the watchlist.

  • removeCryptoFromWatchlistCommand: Remove a crypto from the watchlist.

  • setPriceLimitCommand: Set a price limit for a crypto in the watchlist.

Domain Events:

  • CryptoAddedToWatchlistEvent: A crypto has been added to the watchlist.

  • CryptoRemovedFromWatchlistEvent: A crypto has been removed from the watchlist.

2.4.3. Wallet Context

Manages users' cryptocurrency portfolios by tracking their transactions (buying, selling), current holdings, and overall portfolio valuation. It provides functionalities to view historical performance and assess the value of assets in various currencies. This context ensures that users have a clear and accurate representation of their investment positions.

Ubiquitous Language

Term Definition

Wallet

A list of crypto assets that the logged user owns.

Crypto Asset

A set of data (buying price/date, selling price/date, quantity) related to a crypto that the user owns.

Transaction

A movement of a crypto related to the wallet, which can be a purchase or a sale and is used to track the asset.

Performance

The overall value trend of the wallet based on historical crypto prices, considering any purchase or sale operations.

Report

An analysis of the wallet of a logged user during a timespan.

Interactive Chart

A visual representation that allows users to analyze historical cryptocurrency or wallet data with interactive features such as zoom, time filters, and customizable views.

Entities:

  • Transaction: Represents a transaction related to a crypto asset. It contains the following attributes:

    • transactionID: The unique identifier for the transaction.

    • cryptoID: The unique identifier for the crypto.

    • quantity: The quantity of the crypto bought or sold.

    • doneAt: The date and time when the transaction was executed.

    • priceAtPurchase: The price of the crypto at the time of purchase.

    • currency: The currency used for the transaction (e.g., USD, EUR).

Aggregate Root:

  • Wallet: Represents the user’s wallet of crypto assets. It is an aggregate root that contains a list of Transaction. It has the following attributes:

    • walletID: The unique identifier for the wallet.

Value Objects:

  • Type: Represents the type of transaction (buy or sell).

Services:

  • WalletService: Provides functionalities to manage the wallet, such as adding or removing transactions and generating reports.

Commands:

  • addTransactionCommand: Add a transaction to the wallet.

  • removeTransactionCommand: Remove a transaction from the wallet.

Domain Events:

  • TransactionAddedEvent: A transaction has been added to the wallet.

  • TransactionRemovedEvent: A transaction has been removed from the wallet.

  • WalletReportUpdatedEvent: The wallet performance report has been updated.

2.4.4. Authentication Context

Handles all aspects of user authentication and authorization. This includes user registration, login, secure credential management, and logout.

Ubiquitous Language

User

A person that uses the Application

Authentication

A process where a not-logged user can become a logged user using their credentials

Credentials

A pair of email and password that identify an account

Not\-Logged User

A user who didn\’t register to the system

Logged User

A user registered to the system that can access additional functionality\, like having a personal wallet and a watchlist

Entities:

  • User: Represents a user of the system. It contains the following attributes:

    • userID: The unique identifier for the user.

    • email: The email address of the user.

    • hashedPassword: The hashed password of the user.

Services:

  • AuthenticationService: Provides functionalities for user authentication, such as registration, login, and logout.

Repositories:

  • AuthRepository: Manages the persistence and retrieval of user data.

Commands:

  • signupUserCommand: Register a new user.

  • signinUserCommand: Authenticate a user.

  • logoutUserCommand: Log out a user.

Domain Events:

  • UserRegisterEvent: A new user has been registered.

  • UserSignedInEvent: A user has been authenticated.

  • UserLoggedOutEvent: A user has been logged out.

2.4.5. User Context

Manages user data, including account information. It provides functionalities to view and edit user details, ensuring that users can manage their profiles effectively.

Ubiquitous Language

User

A person that uses the Application

Account

A set of data related to a user

Entities:

  • User: Represents a user of the system. It contains the following attributes:

    • ID: The unique identifier for the user.

    • email: The email address of the user.

    • walletID: The unique identifier for the user’s wallet.

    • watchlistID: The unique identifier for the user’s watchlist.

Value Objects:

  • Profile: Represents the user’s profile information. It contains the following attributes:

    • Name: The first name of the user.

    • Surname: The last name of the user.

    • Date: The date of birth of the user.

Services:

  • UserManagementService: Provides functionalities to manage user data, such as viewing and editing user details.

Factories:

  • UserFactory: Creates instances of the User entity.

Repositories:

  • UserRepository: Manages the persistence and retrieval of user data.

Commands:

  • viewAccountCommand: View user account details.

  • modifyAccountCommand: Edit user account information.

Domain Events:

  • UserAccountModifiedEvent: A user’s account information has been modified.

  • UserInfoUpdatedEvent: A user’s profile information has been updated.

2.4.6. Notification Context

Manages and delivers user-defined alerts based on specific cryptocurrency events or conditions, such as price thresholds or percentage changes. It handles the creation, activation, and triggering of alerts, and sends notifications, like push notifications. This ensures users stay informed about important market movements relevant to their interests.

Ubiquitous Language

Notification

A message from the system sent to a logged user related to a crypto in their watchlist.

Price Limits

A threshold on the price of a crypto \(used to trigger notifications\).

Crypto

A cryptocurrency \(used to specify which crypto the notification is about\).

Aggregate Root:

  • PriceAlert: Represents a price alert set by a user for a specific crypto. It contains the following attributes:

    • alertID: The unique identifier for the alert.

    • userID: The unique identifier for the user who set the alert.

    • cryptoID: The unique identifier for the crypto the alert is related to.

    • alertPrice: The price threshold that triggers the alert.

    • isActive: A flag indicating if the alert is active.

    • message: The message to be sent in the notification.

Value Objects:

  • AlertType: Represents the type of alert.

Services:

  • NotificationServiceImpl: Provides functionalities to manage notifications, such as enabling, disabling, and deleting alerts.

Repositories:

  • PriceAlertRepository: Manages the persistence and retrieval of price alerts.

Commands:

  • enableNotificationCommand: Enable a notification for a crypto.

  • disableNotificationCommand: Disable a notification for a crypto.

  • deleteNotificationCommand: Delete a notification for a crypto.

  • setPriceLimitCommand: Set a price limit for a crypto.

Domain Events:

  • PriceAlertTriggeredEvent: A price alert has been triggered for a crypto.

  • NotificationEnabledEvent: A notification has been enabled for a crypto.

  • NotificationDisabledEvent: A notification has been disabled for a crypto.

  • NotificationDeletedEvent: A notification has been deleted for a crypto.

2.5. Context Map

At the end, after the identification of the Bounded Contexts, the team produced the Context Map that shows the relationships between the contexts. Thanks to a careful analysis, the team decided to regroup the Watchlist, Wallet and User contexts under one Bounded Context called User Management Context. This because the three contexts are strictly related to the user and his data. Here is the Context Map:

Context Map

Customer-Supplier relationship between Bounded Contexts:

  1. Crypto Market Context → Notification Context

    • The Notification Context (Customer) consumes data from the Crypto Market Context (Supplier).

    • The Notification Context relies on price variations from the Crypto Market Context to trigger price alerts.

  2. Crypto Market Context → User Management Context

    • The User Management Context (Customer), specifically its Wallet and Watchlist subcontexts, depends on Crypto Market Context (Supplier) for market data.

    • This data is necessary for tracking user investments (Wallet) and monitoring watched cryptocurrencies (Watchlist).

  3. Authentication Context → User Management Context

    • The User Management Context (Customer) relies on Authentication Context (Supplier) for user authentication and identity management.

    • The User subcontext within User Management needs user credentials and authentication processes managed by Authentication Context.

  4. User Management Context → Notification Context

    • The Notification Context (Customer) depends on User Management Context (Supplier) for watchlist data.

    • This allows Notification Context to trigger alerts based on user preferences and tracked assets.

Conformist releationship between Bounded Contexts:

  1. Crypto Market Context ↔ User Management Context

    • The User Management Context (Downstream) is a Conformist because it must adapt to the Crypto Market Context (Upstream).

    • User Management has no control over how Crypto Market structures its market data and must consume it as provided.

  2. User Management Context ↔ Notification Context

    • The Notification Context (Downstream) is a Conformist because it must accept the User Management Context (Upstream) data model without modifications.

    • Notification Context cannot influence how User Management structures watchlist data.

  3. Authentication Context ↔ User Management Context

    • The User Management Context (Downstream) is a Conformist because it must integrate with Authentication Context (Upstream) without altering its authentication model.

    • User Management must comply with Authentication Context’s predefined user structures and authentication mechanisms.

  4. Crypto Market Context ↔ Notification Context

    • The Notification Context (Downstream) is a Conformist because it must consume price updates from Crypto Market Context (Upstream) without modifying how they are structured.

    • Notification Context adapts to the market data format imposed by Crypto Market Context.

3. Architecture Design

After the analysis of the domain and the production of the bounded context, the next step that the team followed was the design of the architecture.

3.1. Application Design

For the CryptoMonitor system, the team chose a microservices architecture. The microservices architecture is an architectural style that structures an application as a collection of services that are loosely coupled, highly maintainable, and independently deployable. Each service is responsible for a specific business capability and can be developed, deployed, and scaled independently.

The system has been divided into the following microservices following the bounded context:

  • Vue-frontend microservice: This microservice is responsible for the user interface of the system. It is a Vue.js application that consumes the RESTful APIs provided by the backend microservices.

  • Authentication microservice: This microservice is responsible for the authentication and authorization of the users of the system. It provides the necessary APIs for user registration, login, and token generation.

  • Crypto Market microservice: This microservice is responsible for fetching the latest cryptocurrency market data from the CoinGecko API and storing it in the database. It provides the necessary APIs for fetching the market data.

  • User Management microservice: This microservice is responsible for managing the user of the system including his account, watchlist e wallet. It provides the necessary APIs for user management like viewing and editing the account, adding and removing cryptos for the watchlist and adding and removing transactions for the wallet.

  • Notification microservice: This microservice is responsible for sending notifications to the users of the system based on the price alterts that they have set. It provides the necessary APIs for handling the notifications and setting the price alerts.

  • Event-dispatcher microservice: This microservice has been added to the architecture to handle the communication between the microservices. More specifically, it is responsible for dispatching events between the Vue-frontend and Crypto Market and Notification microservices.

The Vue-frontend and the Event-dispatcher microservices doesn’t appear in the bounded context analysis. The first-one because is dedicated only to the frontend and the second-one because is a simple orchestrator between the frontend and the other microservices. However, the team decided to include them in the architecture as a microservices to have a more clear and complete view of the system and also to permit the scalability of the entire system.

The architecture of the system is shown in the following diagram:

Architecture Design

3.2. Interactions

Each microservice interacts with the others through RESTful APIs. The Vue-frontend microservices also use WebSockets(Socket.io) to communicate with the event-dispatcher microservice to receive real-time data.

The team decided to not use an API Gateway in the architecture to avoid unecessary complecity and to keep the system as simple as possible.

3.3. Microservices Design

For each microservice, the team adopted an hexagonal architecture, except for the Vue-frontend microservice that is a Single-Page Application (SPA). The hexagonal architecture is an architectural style that separates the application into three main layers:

  • Domain layer: This layer contains the business logic of the application. It is the core of the application and is independent of the other layers.

  • Application layer: This layer contains the use cases of the application. It is responsible for coordinating the interactions between the domain layer and the infrastructure layer.

  • Infrastructure layer: This layer contains the implementation details of the application. It is responsible for interacting with external systems such as databases, APIs, and messaging systems.

In the following sections is provided a detailed description of all the microservices and their hexagonal architecture.

3.3.1. Authentication

The Authentication microservice is responsible for the authentication and authorization of the users of the system. It provides the necessary APIs for user registration, login, and token handling. The project structure is shown in the following diagram:

packagediagramauth
Figure 4. Package Diagram - Authentication Microservice
  • The business logic of the Authentication microservice is contained in the domain layer, composed by the models prevously identified and the ports.

  • The application layer contains the use cases of the microservice, like the registration and login use cases and orchestrates the interactions between the domain layer and the infrastructure layer.

  • The infrastructure layer is responsible for the interaction with the database and the JWT token generation. In this layer there are the APIs exposed to the other microservices.

3.3.2. Crypto Market

The Crypto Market microservice is responsible for fetching the latest cryptocurrency market data from the CoinGecko API. It provides the necessary APIs for fetching the market data. The project structure is shown in the following diagram:

packagediagramcryptomarket
Figure 5. Package Diagram - Crypto Market Microservice
  • The business logic of the Cryptomarket microservice is contained in the domain layer. This layer is composed of market entity models previously identified and ports.

  • The application layer implements business rules like retrieving market data and managing fetch processes. Orchestrates interactions between the domain and infrastructure layers.

  • The infrastructure layer is responsible for interacting with external systems, such as the CoinGecko API for data retrieval and event handling. Contains adapters for external communication. Components include CryptoRepositoryImpl (for HTTP communication), EventDispatcherAdapter (for event publishing), and a WebServer (for exposing RESTful endpoints).

3.3.3. Event-dispatcher

The Event-dispatcher microservice is responsible for handling the communication between the microservices. It is responsible for dispatching events between the Vue-frontend and Crypto Market and Notification microservices. The project structure is shown in the following diagram:

packagediagrameventdispatcher
Figure 6. Package Diagram - Event-dispatcher Microservice
  • The business logic of the Event-dispatcher microservice is contained in the domain layer. This layer is composed of ports and several models that represent the events that are dispatched between the microservices.

  • The application layer implements business rules like processing events and validating data. Orchestrates interactions between the domain and infrastructure layers.

  • The infrastructure layer is responsible for interacting with external systems, including adapters for WebSockets, event-handling and JWT token validation. Exposes his APIs in the EventAdapter.

3.3.4. Notification

The Notification microservice is responsible for sending notifications to the users of the system based on the price alerts that they have set. It provides the necessary APIs for handling the notifications and setting the price alerts. The project structure is shown in the following diagram:

packagediagramnotification
Figure 7. Package Diagram - Notification Microservice
  • The business logic of the Notification microservice is contained in the domain layer. This layer is composed of notification entity models previously identified and ports.

  • The application layer implements business rules like sending notifications and managing alerts. Orchestrates interactions between the domain and infrastructure layers.

  • The infrastructure layer is responsible for interacting with external systems, including an adapters for DB and event-dispatcher and for validate the JWT token given that the notifications interest only the logged user. Exposes his APIs with a WebServer.

3.3.5. User Management

The User Management microservice is responsible for managing the users of the system including their account, watchlist, and wallet. It provides the necessary APIs for user management like viewing and editing the account, adding and removing cryptos for the watchlist, and adding and removing transactions for the wallet.

packagediagramusermanagement
Figure 8. Package Diagram - User Management Microservice
  • The business logic of the User Management microservice is contained in the domain layer. This layer is composed of user entity models previously identified, ports and a factory responsible for creating new User entities along with their associated Wallet and Watchlist.

  • The application layer implements business rules like managing user accounts, watchlists, and wallets using the commands identified in the DDD. Orchestrates interactions between the domain and infrastructure layers.

  • The infrastructure layer is responsible for interacting with external systems, including adapters for DB communication and APIs exposition of user account, wallet and watchlist. Include also an adapter for the JWT token validation and a middleware to protect the routes reserved to the logged user.

4. DevOps Scenario

4.1. Licensing

The team have selected the MIT License for this project due to its highly permissive terms. This license allows for unrestricted use, modification, and distribution of the software, with minimal limitations. The MIT License provides the flexibility needed for both individual contributors and corporate entities to utilize our software while maintaining appropriate legal protections.

4.2. Versioning

It has been decided to adopt the Semantic Versioning (SemVer) scheme for versioning the software. SemVer consists of three numbers separated by periods: MAJOR.MINOR.PATCH. The MAJOR version is incremented for incompatible changes, the MINOR version is incremented for backward-compatible changes, and the PATCH version is incremented for backward-compatible bug fixes.

To manage the process, the team decided to use the Git Sensitive Semantic Versioning Gradle Plugin, configured with the Conventional Commit Strategy for Git Sensitive Semantic Versioning Gradle Plugin. This approach has allowed to maintain a consistent versioning strategy across the project, and to automate the versioning process based on the commit messages.

The software is released via CI through semantic-release that automatically determines the next version number based on the commit messages and releases the software on GitHub.

4.3. Branch Management

It has been implemented a structured branching strategy for this project to maintain code quality and streamline development workflows.

4.3.1. Branch Organization

The system used two primary branches:

main: Contains production-ready code, updated at project milestones

develop: Collects all updates and ongoing development work

For feature development, separate branches that merged via pull requests into develop upon completion has been created.

4.3.2. Merge Strategy

All merges to the develop branch followed a strict protocol:

  • Only pull requests allowed for merging

  • Rebase the feature branch onto develop before merging

  • Merge strategy enforced (no merge commits)

  • Linear history maintained throughout

  • No primary branches deleted after successful merge

4.3.3. Protection Rules

Through branch rulesets, the team enforced several key protections for the primary branches:

  • Restrict deletions

  • Require signed commits

  • Require a pull request before merging, with merge option not allowed

  • Block force pushes

  • Pull requests can be closed only when the automated tests and linter checks are successful

4.4. Git Hooks

It has been configured Git hooks using Husky to automate and enforce certain checks before commits and pushes. Husky allows to easily manage Git hooks and ensure that our codebase maintains high standards.

The configuration includes the following hooks:

  • pre-commit

  • commit-msg

The pre-commit hook runs before committing, allowing code checks (e.g., linters or formatters). The commit-msg hook runs after the commit message is entered, enforcing message format or content rules.

The configuration is different between Kotlin and Typescript projects, since they use different tools for code quality checks.

(Code for Kotlin Projects - commit-msg)

./gradlew ktlintCheck
npx --no-install commitlint --edit $1

(Code for Typescript Projects - commit-msg)

npx --no-install commitlint --edit $1

(Code for Kotlin Projects - pre-commit)

./gradlew ktLintFormat

(Code for Typescript Projects - pre-commit)

npx lint-staged

4.5. Templates

Three templates were created to standardize the creation of new microservices. The templates are template-for-vue-js, template-for-kotlin, and template-for-typescript.

Each template includes a standardized project structure, configuration files, and build scripts to ensure consistency across microservices. The templates are designed to streamline the development process, reduce setup time, and maintain a high level of code quality.

4.6. Build Automation

Since the project consists of multiple microservices, each service is developed, built, and deployed independently. To ensure a consistent and automated build process, Gradle is used as the primary build tool for both Kotlin and TypeScript microservices, with npm scripts handling specific tasks for TypeScript services.

4.6.1. Implementation strategy for TypeScript microservices

Each TypeScript microservice follows a standardized build.gradle configuration to ensure a consistent and automated build process. Gradle manages dependencies, builds, testing, and running the application, while npm handles JavaScript-specific tasks.

Dependency Management: To ensure that only production dependencies are installed, use:

tasks.register<NpmTask>("installProdDependencies") {
    group = "npm"
    description = "Install only production dependencies (no dev dependencies)"
    args.set(listOf("install", "--omit=dev"))
}

For installing dependencies, two separate tasks are defined:

  • npmCiRoot: Installs dependencies at the root level.

  • npmCiApp: Installs dependencies within the application directory.

Both are combined under:

tasks.register("npmCiAll") {
    group = "npm"
    description = "Install npm dependencies in the root project and in the app directory"
    dependsOn("npmCiRoot", "npmCiApp")
}

Build Process: The build process ensures the application is properly compiled before execution:

tasks.register<NpmTask>("build") {
    dependsOn("npmCiApp")
    args.set(listOf("run", "build"))
}

A cleanup task is also included to remove outdated build artifacts:

tasks.register<Delete>("cleanBuild") {
    group = "build"
    description = "Delete dist and build directories"
    doFirst {
        delete("dist")
        delete("build")
    }
}

Testing: Automated testing is integrated into the workflow. Tests are executed only after the build is complete:

tasks.register<NpmTask>("test") {
    dependsOn("build")
    args.set(listOf("run", "test"))
}

Running the Application: The application can be started in different modes:

  • Production Mode: Runs using npm’s start script.

  • Development Mode: Uses runDev to ensure the application is built before execution.

tasks.register<NpmTask>("start") {
    group = "npm"
    description = "Start the application in production mode"
    args.set(listOf("run", "start"))
}

tasks.register<NpmTask>("runDev") {
    dependsOn("build")
    args.set(listOf("run", "dev"))
}

Preconfigured Workflows: To simplify execution, predefined workflows group multiple tasks together:

  • preRunAll: Cleans the build, installs dependencies, and runs tests.

tasks.register("preRunAll") {
    group = "application"
    description = "Clean, install dependencies and run tests"
    dependsOn("cleanBuild", "npmCiAll", "test")
}
  • allInOne: Executes preRunAll and then starts the application.

tasks.register("allInOne") {
    group = "application"
    description = "Run build and tests, then start the application"
    dependsOn("preRunAll")
    finalizedBy("runDev")
}

Documentation Generation: To generate project documentation using npm:

tasks.register<NpmTask>("docs") {
    dependsOn("npmCiAll")
    workingDir = file("..")
    args.set(listOf("run", "docs"))
}

Versioning: To display the current project version:

tasks.register("printVersion") {
    doLast {
        println("Project version: ${project.version}")
    }
}

4.6.2. Implementation strategy for Kotlin microservices

Kotlin microservices follow a standardized build.gradle configuration to automate building, testing, linting, and packaging. Gradle handles dependency management, static analysis, and Docker integration for streamlined deployment.

Testing: JUnit 5 is used as the testing framework. The configuration ensures that all tests run on the JUnit Platform:

tasks.named<Test>("test") {
    useJUnitPlatform()
}

Code Quality and Static Analysis: To enforce code quality, the project integrates detekt for static analysis and ktlint for code formatting.

  • detekt: Uses a predefined configuration file for linting.

  • ktlintFormat: Ensures the code is formatted correctly.

detekt {
    buildUponDefaultConfig = true
    config.setFrom("config/detekt/detekt.yaml")
}

tasks.named("build") {
    dependsOn("ktlintFormat", "detekt", "test")
}

Documentation: The project uses Dokka to generate documentation. However, due to configuration constraints, caching is disabled:

tasks.withType<org.jetbrains.dokka.gradle.DokkaTask>().configureEach {
    notCompatibleWithConfigurationCache("DokkaTask is not compatible with configuration cache")
}

Packaging the Application: The jar task creates a fat JAR, bundling all runtime dependencies. The main class is specified in the manifest:

tasks.jar {
    archiveFileName.set("app.jar")
    manifest {
        attributes["Main-Class"] = application.mainClass.get()
    }

    // Include all runtime dependencies
    from(
        configurations.runtimeClasspath
            .get()
            .filter { it.name.endsWith("jar") }
            .map { zipTree(it) },
    )

    from(sourceSets.main.get().output)

    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

Versioning: A simple task to print the current version:

tasks.register("printVersion") {
    val version = project.version
    doLast {
        println("Project version: $version")
    }
}

Docker Integration: To streamline containerization, Docker tasks are included:

  • dockerBuild: Builds a Docker image for the application.

  • dockerRun: Runs the application inside a Docker container.

  • dockerClean: Cleans up unused Docker images.

tasks.register<Exec>("dockerBuild") {
    group = "docker"
    description = "Builds the Docker image for the application."
    workingDir = file("..")
    commandLine("docker", "build", "-f", "Dockerfile", "-t", "cryptomarket:latest", ".")
}

tasks.register<Exec>("dockerRun") {
    group = "docker"
    description = "Runs the Docker container for the application."
    dependsOn("dockerBuild")
    commandLine("docker", "run", "-p", "8080:8080", "cryptomarket:latest")
}

tasks.register<Exec>("dockerClean") {
    group = "docker"
    description = "Removes dangling Docker images."
    commandLine("docker", "image", "prune", "-f")
}

4.7. Continuous Integration

At the purpose of making the project more reliable and maintainable, various actions with GitHub Actions has been implemented. The main actions are:

  • Release Workflow

  • Delete Branch on Rebase

  • PR Test Pipeline

  • Release

4.7.1. Release

This workflow automates the release process and is triggered by pushes to the main branch. It consists of a single job that:

  • Checks out the repository with full history to access all tags

  • Sets up Node.js environment

  • Installs project dependencies

  • Runs semantic-release to determine if a release is necessary based on commit messages

If semantic-release determines a release is needed, it automatically:

  • Creates a new version based on commit conventions

  • Generates release notes

  • Creates a GitHub release

  • Tags the repository

The release is done and signed by the CryptoMonitorCI-Bot.

4.7.2. Release Workflow

This workflow is triggered by a published release. It consists of three main jobs:

  • Build and push Docker images:

    • Checks out the repository

    • Logs into GitHub Container Registry (GHCR)

    • Sets up Docker Buildx for multi-platform builds

    • Builds and pushes Docker images with appropriate tags

    • Verifies the pushed image

  • Build documentation:

    • Generates Dokka HTML documentation for Kotlin projects

    • Generates TypeDoc documentation for TypeScript projects

    • Packages documentation files

    • Uploads as workflow artifact

  • Deploy documentation:

    • Downloads the documentation artifact

    • Switches to dokkaDoc branch

    • Updates documentation files

    • Signs and pushes changes with GPG key as CryptoMonitorCI-Bot

4.7.3. Delete Branch on Rebase

This workflow automatically removes branches after they have been successfully merged via pull requests. It doesn’t delete protected branches like Main or Develop due to branch rulesets. The workflow:

  • Triggers on pull request closure

  • Verifies the pull request was merged

  • Checks if the branch is not the default branch

  • Attempts to delete the branch via GitHub API

  • Handles potential errors from branch protection rules

4.7.4. PR Test Pipeline

This workflow runs automated checks on every pull request (creation, update, or reopening). Due to branch protection rules, both jobs must complete successfully before the pull request can be merged.

For Kotlin projects:

  • Test job:

    • Checks out the code

    • Sets up Java environment

    • Configures Gradle caching

    • Executes the test suite

  • Code quality job:

    • Runs Detekt static code analysis

    • Posts analysis results as PR comments

    • Maintains persistent feedback through sticky comments

For TypeScript projects:

  • Test job:

    • Runs Jest test suite

    • Checks test coverage thresholds

    • Reports test results

  • Code quality job:

    • Runs ESLint static analysis

    • Checks TypeScript compilation

    • Posts analysis results as PR comments

    • Maintains persistent feedback through sticky comments

4.8. Shell Scripts

The team developed various shell scripts to automatize and help our work to develop this microservice application.

These scripts are created for both Windows and Unix environments, with .ps1 for PowerShell and .sh for shell scripts. The scripts are:

4.8.1. Docker Service Management Scripts

  • Service Rebuild Script: Rebuilds all microservices except MongoDB and safely restarts MongoDB without stopping it. This preserves database state during development while allowing updates to other services.

  • Service Stop Script: A simple utility that stops all running Docker services in our environment using the docker-compose configuration.

4.8.2. Repository Management Scripts

  • Multi-Repository Update Script: Automates the maintenance of our multiple Git repositories by checking out the develop branch in each project directory and pulling the latest changes. The script provides error handling and reports the success or failure of each repository update operation.

These scripts significantly improved our development workflow by automating repetitive tasks, ensuring consistent development environments, and simplifying the management of our microservice architecture.

4.9. Containerization

Containerization, facilitated by Docker, plays a crucial role in efficiently isolating and distributing applications. Docker containers encapsulate everything needed to run an application, ensuring consistency across various environments. This approach simplifies distribution, versioning, and dependency management, enhancing the overall portability of applications.

In the microservice architecture of the system, each microservice is containerized with its own Dockerfile, enabling independent deployment and scaling. This modular approach allows for isolated development, testing, and production environments while maintaining consistency across the development lifecycle.

4.9.1. Docker Configuration

Separate Dockerfile configurations are maintained for the TypeScript and Kotlin backends, as well as the Vue.js frontend, to accommodate their different runtime requirements:

TypeScript Backend Dockerfile

For TypeScript microservices, is used a multi-stage build process that optimizes the final image size while maintaining all necessary dependencies.

It has been leveraged the build automation through Gradle to standardize the build process, manage dependencies, and ensure consistent builds across environments:

FROM gradle:8.12.1-jdk-alpine AS build
WORKDIR /usr/src/app

RUN apk add --update --no-cache curl nodejs npm

RUN java -version && gradle --version && node -v && npm -v

COPY app/build.gradle.kts settings.gradle.kts ./app/
COPY gradle ./app/gradle
COPY ./gradlew ./gradlew.bat ./app/

COPY . .

RUN gradle build
RUN gradle installProdDependencies

FROM node:22-alpine AS runtime
WORKDIR /app
RUN apk add --update --no-cache curl

COPY --from=build /usr/src/app/app/node_modules ./node_modules
COPY --from=build /usr/src/app/app/dist ./dist
COPY --from=build /usr/src/app/app/package.json ./

EXPOSE 3000

CMD ["npm", "run", "start"]
Kotlin Backend Dockerfile

For Kotlin microservices, is leveraged the Gradle build system and JVM optimization techniques:

FROM gradle:8.12-jdk21 AS build

COPY app/build.gradle.kts settings.gradle.kts gradle.properties ./
RUN mkdir -p gradle
COPY gradle/libs.versions.toml gradle/
COPY app/src src

RUN --mount=type=cache,target=/home/gradle/.gradle/caches gradle jar --no-daemon --parallel --build-cache

FROM openjdk:21-jdk-slim

RUN apt-get update && apt-get install -y curl

WORKDIR /app

COPY --from=build /home/gradle/build/libs/app.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]
Vue.js Frontend Dockerfile

For our Vue.js frontend application, is used a multi-stage build that compiles the application and serves it via Nginx:

FROM node:22.13-alpine AS build
WORKDIR /app
COPY app/package*.json ./
RUN npm install
COPY app/ .
RUN npm run build

FROM nginx:stable as prod-stage
COPY --from=build /app/dist /usr/share/nginx/html
COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 80
COPY ./nginx.conf /etc/nginx/conf.d/nginx.conf.template
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

The entrypoint.sh script is crucial for dynamically configuring Nginx with environment variables, allowing the frontend to communicate with various microservices:

#!/bin/sh

envsubst '\
    \${AUTHENTICATION_SERVICE_NAME} \${AUTHENTICATION_SERVICE_PORT} \
    ${CRYPTOMARKET_SERVICE_NAME} \${CRYPTOMARKET_SERVICE_PORT}
    \${USER_MANAGEMENT_SERVICE_NAME} \${USER_MANAGEMENT_SERVICE_PORT}
    \${EVENT_DISPATCHER_SERVICE_NAME} \${EVENT_DISPATCHER_SERVICE_PORT}
    \${NOTIFICATION_SERVICE_NAME} \${NOTIFICATION_SERVICE_PORT}'< /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/nginx.conf

# Start Nginx
exec "$@"

This script uses envsubst to replace environment variable placeholders in the Nginx configuration template with actual values at container startup time, enabling flexible service discovery in the realized microservices architecture.

Each Docker image is automatically built, tagged, and published to GitHub Container Registry through our CI/CD pipelines, ensuring that the latest version is always available for deployment.

4.10. Documentation

To make the development process more efficient and maintainable, the team has implemented a comprehensive documentation strategy. The documentation is built using AsciiDoc, a lightweight markup language that allows for easy content creation and formatting. In the Documentation Repository, the team has implemented a GitHub Actions workflow to build and deploy the documentation automatically. The workflow is triggered on pushes to the main branch and can also be manually triggered. It consists of two jobs: build and deploy.

The build job checks out the repository, installs necessary packages, sets up Ruby, installs Ruby gems, builds the documentation, and uploads the generated documentation as an artifact.

The deploy job checks out the repository, switches to the gh-pages branch, downloads the documentation artifact, imports the GPG key, and commits and pushes the updated documentation to the gh-pages branch.

The page is then built by GitHub Pages and made available at the CryptoMonitor Documentation URL.

5. Deployment

5.1. Overview

The team implemented a Docker-based deployment approach that allows for both local development and production scenarios.

5.2. Deployment Architecture

The deployment of the architecture follows a containerized microservices pattern, where each component runs in its own Docker container. The application consists of the following services:

  • Frontend: Vue.js web interface exposed to users

  • Authentication: Handles user authentication and token generation

  • User Management: Manages user profiles and preferences

  • Crypto Market: Retrieves and processes cryptocurrency market data

  • Notification: Manages and sends notifications to users

  • Event Dispatcher: Coordinates events between different services

  • MongoDB: Database server storing application data

deployment arc
Figure 9. Deployment Architecture graph

5.3. Docker Compose Configuration

The team decided to maintain two separate Docker Compose configurations for different deployment scenarios:

5.3.1. Local Development Configuration

The local development configuration (docker-composelocale.yml GitHub) builds services directly from source code in local directories. This approach is designed for development purposes, allowing developers to test changes made to individual services without rebuilding images.

# Example from local configuration
services:
  frontend:
    build:
      context: ./src/vue-frontend
      dockerfile: Dockerfile
    # Configuration continues...

5.3.2. Production Deployment Configuration

The production configuration (docker-compose.yml GitHub) uses pre-built images from our GitHub Container Registry. This approach ensures consistent, tested deployments across environments.

# Example from production configuration
services:
  frontend:
    image: ghcr.io/cryptomonitorasw-spe/vue-frontend:latest
    # Configuration continues...

5.4. Network Architecture

The deployment utilizes two distinct Docker networks:

  • Internal network (interna): A bridge network with internal access only, used for service-to-service communication that should not be directly accessible from outside the Docker environment. This provides an additional layer of security by isolating internal services.

  • External network (esterna): A bridge network that allows external access to services that need to be publicly available.

networks:
  interna:
    driver: bridge
    internal: true
    driver_opts:
      com.docker.network.bridge.name: "br-interna89012"
      com.docker.network.enable_ipv6: "false"
  esterna:
    driver: bridge
    internal: false
    driver_opts:
      com.docker.network.bridge.name: "br-esterna89012"
      com.docker.network.enable_ipv6: "false"

5.5. Service Dependencies and Health Checks

The deployment implements dependency management to ensure services start in the correct order. For example, the frontend depends on the event-dispatcher, which depends on other services.

Each service includes health checks that verify its operational status before dependent services are started:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 5s
  timeout: 5s
  retries: 3
  start_period: 5s

This approach prevents cascading failures where dependent services start before their prerequisites are fully operational.

5.6. Environment Variables and Configuration

It has been used the Docker Compose’s anchors and extensions feature to define reusable configuration blocks, making the configuration of the project DRY and easier to maintain:

# Base service definitions with anchors for individual services
x-service-defs:
  authentication-name: &auth-name authentication
  authentication-port: &auth-port 3000
  # More definitions...

# Service-specific environment variables
x-auth-env: &auth-env
  NAME: *auth-name
  PORT: *auth-port
  USER_MANAGEMENT_SERVICE_NAME: *user-name
  USER_MANAGEMENT_SERVICE_PORT: *user-port
  MONGODB_HOST: *mongodb-name
  MONGODB_PORT: *mongodb-port

These environment variables are then injected into each service:

services:
  authentication:
    # Other configuration...
    environment:
      <<: *auth-env
      JWT_SIMMETRIC_KEY: ${JWT_SIMMETRIC_KEY}

Sensitive configuration values like JWT_SIMMETRIC_KEY and COINGECKO_API_KEY are provided through environment variables loaded from a .env file, which is not committed to version control for security reasons.

5.7. Deployment Workflow

5.7.1. Local Development Deployment

For local development, the process is:

  1. Clone the bootstrap repository

  2. Create a src directory within the cloned repository

  3. Clone each service repository (frontend, authentication, user-management, etc.) into the src directory

  4. Create a .env file with required secrets

  5. Run docker-compose -f docker-composelocale.yml up --build

This builds all services from local source code, allowing developers to test changes immediately.

5.7.2. Production Deployment

For production, the workflow is:

  1. Changes are pushed to the main branch

  2. CI/CD pipeline builds and tests the code

  3. Docker images are built and pushed to GitHub Container Registry

  4. On the production server:

    • Clone/pull the repository

    • Create/update the .env file

    • Run docker-compose up -d

This pulls the pre-built images from the registry and deploys them according to the production configuration.

5.8. Monitoring

Each service exposes a /health endpoint that can be used for monitoring. In a production environment, these endpoints can be integrated with monitoring tools to provide alerts and visualization of system health.

6. Conclusion

This software process engineering project has served as a comprehensive academic exercise in applying modern software development methodologies to a complex domain. The CryptoMonitor application represents not merely a functional product, but more significantly, a practical demonstration of software engineering principles in action.

6.1. Key Achievements

The project’s principal academic value lies in the successful integration of multiple software engineering paradigms. We implemented a microservices architecture following domain-driven design principles, establishing clear bounded contexts with appropriate separation of concerns. This architectural approach provided practical insights into the theoretical concepts discussed throughout our coursework. The containerized deployment strategy using Docker demonstrates our understanding of contemporary deployment methodologies, while the implementation of CI/CD pipelines using GitHub Actions reflects our application of DevOps principles. These technical achievements were complemented by our methodological approach, utilizing event storming techniques and agile practices to systematically address the complexity inherent in the cryptocurrency domain.

6.2. Challenges and Solutions

The academic nature of this project allowed us to confront and resolve challenges that exemplify core software engineering problems. The cryptocurrency domain presented significant complexity in terms of business rules and terminology, requiring rigorous domain analysis. Our response—conducting thorough event storming sessions and developing a comprehensive ubiquitous language—demonstrates the practical application of domain modeling techniques. The communication patterns between microservices represented a fundamental architectural challenge. Our solution implemented an event-dispatcher service as an orchestrator, illustrating the application of design patterns to solve architectural problems. Similarly, the deployment complexities we faced required thoughtful consideration of service dependencies and health monitoring, resulting in a Docker Compose configuration that addresses these concerns systematically. Testing distributed systems presented methodological challenges that are rarely addressed in simpler academic exercises. By implementing standardized test pipelines across different programming languages, we developed practical experience with testing strategies for distributed architectures.

6.3. Learning Outcomes

This project has transformed theoretical knowledge into practical understanding across multiple dimensions of software engineering. We have gained empirical insights into the relationship between domain modeling and architecture design, experiencing how theoretical concepts manifest in practical implementation. The management of complexity through microservices architecture has provided concrete experience with the benefits and challenges of this approach. The implementation of automation throughout the development lifecycle has demonstrated its value in ensuring consistent quality and productivity. Working in a multi-language environment has provided practical experience with maintaining coherence across diverse technological components. Perhaps most significantly, we have developed practical understanding of deployment and monitoring techniques for distributed systems.

6.4. Future Academic Exploration

While the current implementation satisfies the project requirements, several areas warrant further technical investigation in an academic context:

  • Implementation of auto-scaling capabilities for handling variable loads

  • Enhanced monitoring and observability through integration with tools like Prometheus and Grafana

  • Implementation of a more sophisticated notification system with multiple channels

  • Exploration of blockchain integration for direct transaction capabilities

These improvements would provide valuable opportunities to apply advanced software engineering concepts and further explore the technical challenges inherent in distributed systems at scale.

In conclusion, this project has demonstrated the application of software process engineering principles in creating a robust, maintainable, and deployable software system. The experience has reinforced our understanding of modern software development practices and prepared us for the challenges of professional software engineering in complex domains.