Visit the live version of our project here:
Tennis Scoreboard.
If you are interested in learning more about dev stuff, you may find my article helpful, available here:
My blog
💡 My Goals (Beyond the Technical Task)
🌱 How to run the app on the local machine with Docker
- Gain practical experience with Server-Side Rendering (SSR) using Java Server Pages (JSP).
- Get acquainted with HTML and CSS.
- Implement input validation using the Jakarta Validation Library.
- Enhance my understanding of typical application architecture.
- Select and practically apply some of the design patterns.
![]() Java |
Maven |
Flyway |
Hibernate |
Tomcat |
![]() JUnit |
JSP |
Glassfish |
HTML |
CSS |
![]() Docker |
![]() CI/CD |
![]() H2 |
-
🛠 Maven: A build and dependency management tool.
-
🗄 H2 Database: In-memory database.
-
📦 Flyway: Database migration and version control.
-
🏗 Hibernate: ORM framework to manage database interactions using Java objects.
- 🔗 Hibernate HikariCP: Database connection pooling.
- ✅ Hibernate Validator: Jakarta Bean Validation for input validation.
-
🔄 ModelMapper: Object-to-DTO mapping.
-
🎨 HTML, CSS: For providing the user interface and styling for the application.
-
💻 Server-Side Rendering:
- 📝 JSP (JavaServer Pages): Server-side templating for dynamic HTML.
- 📚 Standard APIs for building dynamic web content.
- ✨ Jakarta Expression Language (EL) API: Expression evaluation within JSP.
- 🔖 Jakarta Servlet Tag Library (JSTL) APIs: Provides standard tags for common web tasks (e.g., loops, conditions, and formatting).
- 🏗 GlassFish (by Sun Microsystems): The reference implementation of Jakarta EE, including Jakarta EL and JSTL APIs.
-
🌐 Servlet API: For handling client-server communication.
-
🚀 Tomcat: A servlet container that serves as the implementation of the Servlet API and hosts the web application.
The com.aleos.match package encapsulates the object-oriented logic for tennis match progression, including games, sets, and entire matches. At the heart of this package is the AbstractStage class, which serves as the foundational template for all match stages. This class implements the Template Method design pattern, providing a structured framework that standardizes stage behavior while allowing flexibility through polymorphism and inheritance. Concrete implementations of each stage—StandardGame, StandardSet, and StandardMatch—extend AbstractStage to apply specific scoring strategies and manage state transitions specific to tennis.
In attempting to use Redis for match management, I encountered fundamental architectural flaws where functionality and state were inseparable, leading me to switch to ConcurrentHashMap for the ongoing match state management due to easier handling and serialization challenges.
Using Hibernate ORM mandates a validation mechanism that adheres to the Java Bean Validation standard. For this, the project utilizes Hibernate Validator (JSR 380), which is also commonly used in Spring applications.
<dependencies>
<!-- Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<!-- Jakarta Expression Language API -->
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>6.0.1</version>
</dependency>
<!-- Jakarta EL Implementation -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>
Jakarta Expression Language (EL):
Hibernate Validator requires an implementation of the Jakarta Expression Language for dynamically parsing expressions in validation messages. Notably, this dependency is not included by default, likely in adherence to the principle of minimal dependencies, allowing developers to choose their preferred EL implementation.
If EL expressions are not needed, the ValidationFactory
can be obtained as follows:
@Bean
public Validator validator(@Bean(name = "validationFactory") ValidatorFactory validationFactory) {
return Validation.byDefaultProvider()
.configure()
.messageInterpolator(new ParameterMessageInterpolator())
.buildValidatorFactory();
}
This configuration uses ParameterMessageInterpolator
instead of the default ResourceBundleMessageInterpolator
.
-
ValidatorFactory Initialization:
ValidatorFactory
is a heavyweight object as it initializes and configures the validation infrastructure. This includes creatingValidator
instances, managing metadata, and caching information about validation constraints. -
Using Validator: When a
Validator
is used to validate an object, it scans the class for validation annotations and generates metadata for it. This metadata includes all validation constraints applicable to the class. -
Metadata Caching: To improve performance, metadata about validation constraints are cached, speeding up the validation process for subsequent validations of the same class.
-
Validation Process: During validation,
Validator
uses reflection to analyze all fields, classes, and methods of the target object that bear validation annotations (e.g.,@NotNull
,@Size
). If data does not meet the expected constraints, error messages are generated.
- Implemented the Service Locator pattern via
BeanFactory
, set up inAppConfiguration
using custom@Bean
annotations.
public class AppConfiguration {
@Bean
public MatchService matchService(@Bean(name = "matchDao") MatchRepository matchDao,
@Bean(name = "playerDao") PlayerRepository playerDao) {
return new MatchService(matchDao, playerDao);
}
// Additional configuration methods...
}
- To ensure the beans are not lost,
BeanFactory
must be preserved withinServletContext
. This is accomplished using theServletContextListener
interface.
// Integration with ServletContext
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
injectFactoryBean(sce);
}
private static void injectFactoryBean(ServletContextEvent sce) {
var factory = new BeanFactory(AppConfiguration.class);
sce.getServletContext().setAttribute(AppContextAttribute.BEAN_FACTORY.name(), factory);
}
}
Implementing pagination and filtering turned out to be more challenging than initially anticipated. Initially, I found myself confused by the multitude of parameters; to resolve this, I encapsulated them into a pair of objects for filtration and pagination. However, distinguishing which parameters related to which aspect proved tricky. After several iterations, I managed to refine the classes MatchFilterCriteria and PageableInfo into functional components.
The subsequent challenge was maintaining a functional repository layer during these evolutionary changes. This led me to explore the Criteria API, which ultimately resolved all my issues related to retrieving the correct data based on pagination and filters effectively.
Key Insights:
Indexing for Filtering Fields: It's crucial to implement indexes on filtering fields to enhance query performance and ensure efficient data retrieval.
Avoiding Offset in Pagination: Current observations suggest that using an offset in database queries can be counterproductive. It's more effective to construct pagination solutions based on specific WHERE clauses. This approach facilitates the use of indexes, whereas using offsets can lead to sequential processing that heavily loads the database.
- Factory Pattern (StageFactory)
- Strategy Pattern (ScoringStrategy)
- Observer Pattern (AbstractStage)
- Template Method Pattern (AbstractStage<T extends Stage)
- Service Locator Pattern (BeanFactory)
Java Server Pages (JSP) is a technology that mixes HTML, Expression Language (EL), and Java scriptlets, facilitating the creation of dynamically generated web pages. Although JSP allows for scriptlets—Java code embedded directly within the HTML—it is generally considered poor practice to mix presentation with logic extensively. Instead, the use of JSP tag libraries is recommended. These libraries offer a concise way to embed logic into HTML using custom tags, reducing the need for scriptlets.
JSPs serve as templates that are interpreted and compiled by the Tomcat server's JSP engine, Jasper. During this process, HTML code is treated as strings, while EL expressions and custom tags are converted into executable Java code within the generated servlet. This transformation allows dynamic content to be seamlessly integrated and delivered as part of static HTML, ready for client-side rendering.
This architecture ensures that JSP remains a powerful yet manageable approach for generating dynamic web content, albeit with considerations for best practices in web development.
HTML (HyperText Markup Language) is the standard markup language used to create and structure content on the web. It forms the backbone of any web page, defining the skeletal layout and assembly of various page elements. HTML uses a series of elements to encapsulate different types of content, ensuring web browsers know how to display each element correctly.
Elements are defined by tags. These elements can be nested, allowing for complex web page structures. Attributes within the tags provide additional settings or properties for the elements, such as setting a hyperlink’s destination with the href attribute in an tag.
HTML documents are essentially a hierarchy of elements, forming what is known as the DOM (Document Object Model), which scripts like JavaScript can manipulate to dynamically change the displayed content. This makes HTML not just a static skeleton but a dynamic foundation that interacts with user actions and scripting languages to create the rich, interactive web experiences familiar today.
By defining the content structure, HTML lays the groundwork for CSS and JavaScript, which respectively style and add interactivity to the web content, demonstrating HTML’s pivotal role in web development.
While HTML forms the hierarchical structure of web elements, CSS acts as their stylist, enabling the separation of presentation from content. This separation enhances accessibility and flexibility, allowing for detailed control over the layout, colors, and fonts without altering the HTML.
CSS operates by applying specific rules to targeted elements, reminiscent of inheritance in object-oriented programming (OOP), where properties are dynamically determined based on a set of cascading rules. These rules dictate how elements are styled and are applied based on criteria such as specificity, importance, and the source order of the CSS declarations.
Mastering CSS involves understanding its complexity and the nuances of how styles are applied, which can be a significant investment of time. My beginner attempting to use CSS cold only grasp the basics.
Despite the errors I made, this project provided a wealth of new experiences and the opportunity to refine technologies I was already familiar with. It was undoubtedly worth the time invested, and it stands out as one of the best projects I have personally undertaken to date. This experience not only deepened my technical skills but also reinforced the value of learning through practical application, making it a rewarding endeavor in my development journey.
The deployment process for the Tennis-Scoreboard application is automated using GitHub Actions and follows a CI/CD pipeline. The process ensures that the application is built, tested, and deployed.
The pipeline consists of two main stages:
- Release Build Process
- Deployment to Remote Server
Each of these stages is executed through GitHub Actions workflows configured in release.yml and deploy.yml.
The release process is triggered when a tag is pushed to the repository.
-
Trigger: The process starts when a Git tag is pushed (on: push: tags: '*').
-
Build and Packaging:
- The source code is checked out from the repository.
- The JDK 21 (Eclipse Temurin) environment is set up.
- The Maven build tool is used to compile and package the application into a .war file.
- After the build, the .war file (tennis-scoreboard.war) is copied to a designated directory.
- This .war file is uploaded as a build artifact to GitHub for further processing.
-
Release:
- Once the artifact is uploaded, the second stage of the job downloads the .war file.
- The ncipollo/release-action GitHub action is used to publish a release in the GitHub repository. The artifact is attached to the release, and this release is accessible for deployment purposes.
The deployment is handled through the deploy.yml file, which is triggered when the release process is completed (on: workflow_run).
- SSH into Remote Server: The workflow uses appleboy/ssh-action to remotely connect to the server where the application is hosted.
- Tomcat Restart: The running Tomcat server is stopped using the shutdown.sh script.
- Backup the Current WAR File: If an existing WAR file is found, it is backed up by renaming it (e.g., tennis-scoreboard.war.bak).
- Download Latest Release: The workflow dynamically retrieves the latest release from the GitHub repository using the GitHub API. The downloaded WAR file is placed in the Tomcat webapps directory.
- Set File Permissions: The file permissions are adjusted so that Tomcat can read and execute the new WAR file.
- Start Tomcat: The Tomcat server is restarted using the startup.sh script to deploy the new version of the application.
-
Front-End Server (Apache2): The application is hosted on a remote server where Apache2 acts as the front-end web server. Apache handles all incoming HTTP (port 80) and HTTPS (port 443) traffic and serves as a proxy to the Tomcat server. Apache is responsible for managing SSL certificates and encrypting traffic.
-
Reverse Proxy to Tomcat: Apache2 is configured to proxy all incoming requests for the subdomain (tennis-scoreboard.ale-os.com) to a Tomcat server running in the background on the same machine. Apache forwards the requests to Tomcat, which is bound to localhost. Tomcat handles the application logic and serves the content back to Apache, which then returns the response to the client.
-
Tomcat Configuration: Tomcat runs as a background service on the remote server and is configured to serve the application via localhost. The WAR file is deployed in Tomcat's webapps directory, and Apache2 forwards requests to Tomcat using proxy directives. Tomcat does not handle SSL directly; instead, Apache takes care of encrypting the communication and proxies the traffic securely to Tomcat.
-
SSL Configuration: SSL certificates are managed by Apache2 using Let's Encrypt. All HTTPS traffic is terminated at Apache, and the requests are then proxied to Tomcat over unencrypted HTTP.
Several secrets are used within the CI/CD pipeline to ensure security:
- SSH Keys: The deployment process uses SSH keys (secrets.SSH_AWS) to connect securely to the remote server.
- GitHub Token: The release action uses a GitHub token (secrets.FOR_RELEASE_TOKEN) to create and upload releases.
- Username and Host: The username and host details for the remote server are stored securely in GitHub secrets ( secrets.USERNAME, secrets.REMOTE_HOST).
This CI/CD pipeline automates the build, release, and deployment process for the Tennis-Scoreboard application. Using GitHub Actions, Maven, and Tomcat, the system ensures that each new release is built and deployed to production with minimal manual intervention.
Follow these steps to get the project up and running on your local machine.
Before you begin, ensure you have the following software installed and available:
- Docker: Download Docker
Ensure that port 8080 on localhost are free for use.
- Download and Extract the Project:
-
Download the project ZIP file from the repository.
-
Unzip the file to your desired directory.
-
Navigate to the Project Directory:
cd yourprojectdirectory
-
Build and Run the Docker Containers:
docker build -t tennis-scoreboard . docker run -p 8080:8080 --name tennis-scoreboard tennis-scoreboard
-
Access the Frontend:
Once the project is running, you can access the frontend at:
http://localhost:8080/
I am continuously looking to refine my understanding and implementation of programming. If you have insights, critiques, or advice—or if you wish to discuss any aspect of this project further—I warmly welcome your contributions. Please feel free to open an issue to share your thoughts.
I want to express my gratitude to the author of the
technical requirementsfor this project,
S. Zhukov`. I would also like to thank A. Shinchik, a fellow developer, for assisting with the testing of the first working version of this application.
As a backend developer, I used to think handling millions of requests per second was hard... then I tried aligning a div in CSS.
When I started working on the frontend, I thought async requests were tough. Then I met my true enemy: browser compatibility.