This repository contains a production-ready, cloud-native RESTful API web application built with Spring Boot for user management, product catalog, and image storage. The application is designed for deployment on AWS with comprehensive monitoring, automated infrastructure provisioning, and CI/CD pipelines.
- Overview
- Features
- Architecture
- Tech Stack
- Prerequisites
- Development Setup
- API Documentation
- Database Schema
- Security
- Cloud Infrastructure
- Monitoring and Observability
- CI/CD Pipelines
- Testing
- Configuration
- Deployment
- Error Handling
- Project Structure
This is a stateless, horizontally-scalable REST API that provides:
- User Management: Secure user registration and profile management
- Product Catalog: Full CRUD operations with ownership-based access control
- Image Storage: AWS S3-backed image upload and management for products
- Health Monitoring: Application and database health checks
- Auto-Scaling Architecture: Deployed with AWS Auto Scaling Groups (3-5 instances) behind Application Load Balancer
- High Availability: Multi-AZ deployment with automatic failover and dynamic scaling
- Cloud-Native Design: Fully automated infrastructure provisioning via Terraform
The application follows microservice best practices with comprehensive logging, metrics, and security measures.
- User Management: User registration, authentication, profile retrieval and updates
- Product Management: Complete CRUD operations on products with ownership-based authorization
- Image Management: Upload, retrieve, list, and delete product images stored in AWS S3
- Health Checks: Database connectivity verification and application health monitoring
- Authentication: HTTP Basic Authentication with BCrypt password hashing (strength 10)
- Authorization: Ownership-based access control for products and user data
- Password Policy: Enforced complexity requirements (8-15 chars, uppercase, lowercase, digit, special char)
- Input Validation: Comprehensive validation for email format, SKU uniqueness, quantity constraints
- Stateless Design: No session storage, horizontally scalable architecture
- Security Headers: Cache-control and X-Content-Type-Options headers on all responses
- Auto Scaling: Dynamic instance management with AWS Auto Scaling Groups
- Minimum: 3 instances
- Maximum: 5 instances
- Scale up when CPU > 5%, scale down when CPU < 3%
- Spans multiple availability zones for high availability
- Load Balancing: Application Load Balancer for traffic distribution
- HTTP traffic on port 80 forwarded to application port 8080
- Health checks via
/healthzendpoint - Provides static DNS endpoint for dynamic instance pool
- AWS S3: Persistent image storage with organized path structure (
users/{userId}/products/{productId}/{uuid-filename}) - CloudWatch: Centralized logging and metrics aggregation
- Route53: DNS management with subdomain delegation (dev/demo environments)
- Packer: Automated AMI builds with application pre-installed
- Terraform: Infrastructure as Code for reproducible deployments
- Structured Logging: JSON-formatted logs with request ID tracking via Logstash encoder
- Metrics Export: Micrometer integration with StatsD protocol for CloudWatch
- Request Tracing: Unique request IDs assigned via MDC for distributed tracing
- API Monitoring: Automatic timing and call counting for all endpoints
- Database Monitoring: Query performance tracking via Hibernate statistics
- Testing: 91% code coverage with 160 comprehensive integration and unit tests
- CI/CD: Automated testing on every pull request with branch protection
- Code Quality: JaCoCo coverage reports and Maven quality checks
┌─────────────┐ ┌──────────────┐ ┌──────────────────────────┐
│ Client │────▶│ Route53 │────▶│ Application Load │
│ (HTTP) │ │ (DNS) │ │ Balancer (Port 80) │
└─────────────┘ └──────────────┘ └──────────┬───────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Application │ │ Application │ │ Application │
│ Instance 1 │ │ Instance 2 │ │ Instance 3 │
│ (Spring) │ │ (Spring) │ │ (Spring) │
└───────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ MySQL │ │ AWS S3 │ │ CloudWatch │
│ Database │ │ (Images) │ │(Logs/Metrics│
└─────────────┘ └─────────────┘ └─────────────┘
- Controller Layer: REST endpoint handlers (
HealthController,UserController,ProductController,ImageController) - Service Layer: Business logic (
UserService,ProductService,ImageService,S3Service,MetricsService) - Repository Layer: Data access via JPA repositories
- Entity Layer: JPA entities (
User,Product,Image,HealthCheck) - DTO Layer: Data transfer objects for request/response mapping
- Filter Layer:
MetricsFilterfor API timing,RequestLoggingFilterfor request tracing - Configuration Layer: Security, S3, and application configuration
- Repository Pattern: Data access abstraction via Spring Data JPA
- DTO Pattern: Separation of internal entities from API contracts
- Service Layer Pattern: Business logic encapsulation
- Filter Chain Pattern: Request processing pipeline for metrics and logging
- Dependency Injection: Spring Framework IoC container
- Builder Pattern: Entity construction and updates
- Spring Boot: 3.5.6
- Java: 21 (JRE for production, JDK for development)
- Maven: 3.6+ for dependency management and builds
- MySQL: 8.0+
- JPA/Hibernate: ORM with automatic schema management
- HikariCP: Connection pooling (default with Spring Boot)
- Spring Security: Authentication and authorization framework
- BCrypt: Password hashing with strength 10
- AWS SDK v2: S3 client for image storage
- AWS CloudWatch: Logging and metrics
- Packer: AMI image building
- Systemd: Service management on Ubuntu
- JUnit 5: Test framework
- Mockito: Mocking framework
- REST Assured: HTTP API testing
- Spring Boot Test: Integration test support
- JaCoCo: Code coverage reporting
- Micrometer: Metrics collection facade
- StatsD: Metrics aggregation protocol
- Logback: Logging framework
- Logstash Encoder: JSON-formatted logging for CloudWatch
- GitHub Actions: Automated workflows for testing and AMI builds
- Maven Surefire: Test execution during builds
- Java 21 or higher
- Maven 3.6+ for dependency management and building
- MySQL 8.0+ database server
- Git for version control
- AWS Account with permissions for EC2, S3, and CloudWatch
- Packer 1.8+ for AMI builds
- AWS CLI configured with credentials
- Install and start MySQL server
- Create a database user with privileges:
CREATE USER 'csye6225'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON webapp.* TO 'csye6225'@'localhost';
FLUSH PRIVILEGES;- The application will automatically create the
webappdatabase on first run if it doesn't exist
git clone git@github.com:dugganite-cloud-computing/webapp.git
cd webappSet environment variables for your local MySQL instance:
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=webapp
export DB_USER=csye6225
export DB_PASSWORD=your_passwordexport AWS_REGION=us-east-1
export S3_BUCKET_NAME=your-bucket-name
# Or configure AWS credentials via ~/.aws/credentialsmvn clean compile# Run all tests
mvn test
# Run specific test class
mvn test -Dtest=UserIntegrationTest
# Run tests with coverage
mvn clean test jacoco:reportmvn jacoco:reportCoverage report will be available at target/site/jacoco/index.html
mvn clean packageThis creates target/webapp-0.0.1-SNAPSHOT.jar
# Using Maven
mvn spring-boot:run
# Or run the JAR directly
java -jar target/webapp-0.0.1-SNAPSHOT.jarThe application will:
- Start on
http://localhost:8080 - Connect to MySQL using configured credentials
- Create the database and tables automatically if they don't exist
- Start exporting metrics to StatsD on localhost:8125 (if available)
http://localhost:8080
Most endpoints require HTTP Basic Authentication:
Authorization: Basic <base64-encoded-credentials>
- Username must be a valid email address
- Credentials format:
email:password
All responses include:
Cache-Control: no-cache, no-store, must-revalidatePragma: no-cacheX-Content-Type-Options: nosniff
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/healthz |
GET | No | Health check |
/v1/user |
POST | No | Create user |
/v1/user/{userId} |
GET | Yes | Get user (own data) |
/v1/user/{userId} |
PUT | Yes | Update user (own data) |
/v1/product |
POST | Yes | Create product |
/v1/product/{productId} |
GET | No | Get product |
/v1/product/{productId} |
PATCH | Yes | Partial update (owner) |
/v1/product/{productId} |
PUT | Yes | Full update (owner) |
/v1/product/{productId} |
DELETE | Yes | Delete product (owner) |
/v1/product/{productId}/image |
POST | Yes | Upload image (owner) |
/v1/product/{productId}/image |
GET | No | List images |
/v1/product/{productId}/image/{imageId} |
GET | No | Get image details |
/v1/product/{productId}/image/{imageId} |
DELETE | Yes | Delete image (owner) |
Check application and database health.
Request:
- No authentication required
- No query parameters allowed
- No request body allowed
Response:
200 OK- Application healthy, database connected400 Bad Request- Query parameters or request body present405 Method Not Allowed- Method not supported (only GET allowed)503 Service Unavailable- Database unreachable
Example:
curl -X GET http://localhost:8080/healthzCreate a new user account.
Request:
- No authentication required
- No query parameters allowed
Request Body:
{
"first_name": "Johnny",
"last_name": "Test",
"username": "johnny@email.com",
"password": "Password123!"
}Validation Rules:
username: Valid email format, unique, requiredpassword: 8-15 characters with uppercase, lowercase, digit, special char (!@#$%^&*()_+-=[]{}|;:',.<>?/~`)first_name: Required, max 255 characterslast_name: Required, max 255 characters- Read-only fields (
id,account_created,account_updated) cannot be provided
Response:
201 Created- User created successfully
{
"id": 1,
"first_name": "Johnny",
"last_name": "Test",
"username": "johnny@email.com",
"account_created": "2025-11-04T10:30:00",
"account_updated": "2025-11-04T10:30:00"
}400 Bad Request- Validation failed or duplicate username503 Service Unavailable- Database unreachable
Example:
curl -X POST http://localhost:8080/v1/user \
-H "Content-Type: application/json" \
-d '{
"first_name": "Johnny",
"last_name": "Test",
"username": "johnny@email.com",
"password": "Password123!"
}'Retrieve user information.
Request:
- Authentication required (user can only access their own data)
- No query parameters allowed
- No request body allowed
Path Parameters:
userId: User ID (must match authenticated user's ID)
Response:
200 OK- User retrieved successfully
{
"id": 1,
"first_name": "Johnny",
"last_name": "Test",
"username": "johnny@email.com",
"account_created": "2025-11-04T10:30:00",
"account_updated": "2025-11-04T10:30:00"
}400 Bad Request- Query parameters or request body present401 Unauthorized- No authentication provided403 Forbidden- Trying to access another user's data404 Not Found- User doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X GET http://localhost:8080/v1/user/1 \
-u "johnny@email.com:Password123!"Update user information.
Request:
- Authentication required (user can only update their own data)
- No query parameters allowed
Path Parameters:
userId: User ID (must match authenticated user's ID)
Request Body (all fields optional):
{
"first_name": "John",
"last_name": "Doe",
"password": "NewPassword123!"
}Validation Rules:
password: Same requirements as registration if providedfirst_name: Max 255 characters, cannot be empty if providedlast_name: Max 255 characters, cannot be empty if provided- Read-only fields (
id,username,email,account_created,account_updated) cannot be provided
Response:
204 No Content- User updated successfully400 Bad Request- Validation failed or read-only fields provided401 Unauthorized- No authentication provided403 Forbidden- Trying to update another user's data404 Not Found- User doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X PUT http://localhost:8080/v1/user/1 \
-u "johnny@email.com:Password123!" \
-H "Content-Type: application/json" \
-d '{
"first_name": "John",
"password": "NewPassword456!"
}'Create a new product.
Request:
- Authentication required
- No query parameters allowed
Request Body:
{
"name": "Obsidian",
"description": "Nature's sharpest blade. Volcanic glass sharper than surgical scalpels",
"sku": "UNIQUE-SKU-123",
"manufacturer": "Fantasia Materials",
"quantity": 5
}Validation Rules:
name: Required, max 255 characters, cannot be emptydescription: Required, cannot be emptysku: Required, unique across all products, cannot be emptymanufacturer: Required, max 255 characters, cannot be emptyquantity: Required, must be >= 0- Read-only fields (
id,date_added,date_last_updated,owner_user_id) cannot be provided
Response:
201 Created- Product created successfully
{
"id": 1,
"name": "Obsidian",
"description": "Nature's sharpest blade. Volcanic glass sharper than surgical scalpels",
"sku": "UNIQUE-SKU-123",
"manufacturer": "Fantasia Materials",
"quantity": 5,
"date_added": "2025-11-04T10:35:00",
"date_last_updated": "2025-11-04T10:35:00",
"owner_user_id": 1
}400 Bad Request- Validation failed or duplicate SKU401 Unauthorized- No authentication provided503 Service Unavailable- Database unreachable
Example:
curl -X POST http://localhost:8080/v1/product \
-u "johnny@email.com:Password123!" \
-H "Content-Type: application/json" \
-d '{
"name": "Obsidian",
"description": "Volcanic glass",
"sku": "UNIQUE-SKU-123",
"manufacturer": "Fantasia Materials",
"quantity": 5
}'Retrieve product information (public endpoint).
Request:
- No authentication required
- No query parameters allowed
- No request body allowed
Path Parameters:
productId: Product ID
Response:
200 OK- Product retrieved successfully
{
"id": 1,
"name": "Obsidian",
"description": "Volcanic glass sharper than surgical scalpels",
"sku": "UNIQUE-SKU-123",
"manufacturer": "Fantasia Materials",
"quantity": 5,
"date_added": "2025-11-04T10:35:00",
"date_last_updated": "2025-11-04T10:35:00",
"owner_user_id": 1
}400 Bad Request- Query parameters or request body present404 Not Found- Product doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X GET http://localhost:8080/v1/product/1Partially update product (owner only).
Request:
- Authentication required (must be product owner)
- No query parameters allowed
Path Parameters:
productId: Product ID
Request Body (all fields optional):
{
"name": "Updated Obsidian",
"quantity": 10
}Validation Rules:
name: Max 255 characters, cannot be empty if provideddescription: Cannot be empty if providedsku: Must be unique if changedmanufacturer: Max 255 characters, cannot be empty if providedquantity: Must be >= 0 if provided- Read-only fields cannot be provided
Response:
204 No Content- Product updated successfully400 Bad Request- Validation failed401 Unauthorized- No authentication provided403 Forbidden- Not the product owner404 Not Found- Product doesn't exist503 Service Unavailable- Database unreachable
Fully replace product (owner only).
Request:
- Authentication required (must be product owner)
- No query parameters allowed
- All fields required in request body
Path Parameters:
productId: Product ID
Request Body:
{
"name": "New Obsidian",
"description": "Updated description",
"sku": "NEW-SKU-456",
"manufacturer": "New Manufacturer",
"quantity": 20
}Response:
204 No Content- Product updated successfully400 Bad Request- Validation failed or missing required fields401 Unauthorized- No authentication provided403 Forbidden- Not the product owner404 Not Found- Product doesn't exist503 Service Unavailable- Database unreachable
Delete product and all associated images (owner only).
Request:
- Authentication required (must be product owner)
- No query parameters allowed
- No request body allowed
Path Parameters:
productId: Product ID
Response:
204 No Content- Product deleted successfully (also deletes all images from S3)400 Bad Request- Query parameters or request body present401 Unauthorized- No authentication provided403 Forbidden- Not the product owner404 Not Found- Product doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X DELETE http://localhost:8080/v1/product/1 \
-u "johnny@email.com:Password123!"Upload an image for a product (owner only).
Request:
- Authentication required (must be product owner)
- Content-Type:
multipart/form-data - Form field name:
file
Path Parameters:
productId: Product ID
File Requirements:
- Allowed types: JPEG, JPG, PNG only
- Validated by Content-Type header
Response:
201 Created- Image uploaded successfully
{
"image_id": 1,
"product_id": 1,
"file_name": "product-image.jpg",
"date_created": "2025-11-04T10:40:00",
"s3_bucket_path": "users/1/products/1/uuid-product-image.jpg"
}400 Bad Request- Invalid file type or no file provided401 Unauthorized- No authentication provided403 Forbidden- Not the product owner404 Not Found- Product doesn't exist503 Service Unavailable- Database or S3 unreachable
S3 Path Structure:
users/{userId}/products/{productId}/{uuid-filename}
Example:
curl -X POST http://localhost:8080/v1/product/1/image \
-u "johnny@email.com:Password123!" \
-F "file=@/path/to/image.jpg"List all images for a product (public endpoint).
Request:
- No authentication required
- Query parameters allowed (unlike other endpoints)
Path Parameters:
productId: Product ID
Response:
200 OK- List of images
[
{
"image_id": 1,
"product_id": 1,
"file_name": "image1.jpg",
"date_created": "2025-11-04T10:40:00",
"s3_bucket_path": "users/1/products/1/uuid-image1.jpg"
},
{
"image_id": 2,
"product_id": 1,
"file_name": "image2.png",
"date_created": "2025-11-04T10:45:00",
"s3_bucket_path": "users/1/products/1/uuid-image2.png"
}
]404 Not Found- Product doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X GET http://localhost:8080/v1/product/1/imageGet specific image details (public endpoint).
Request:
- No authentication required
- Query parameters allowed
Path Parameters:
productId: Product IDimageId: Image ID
Response:
200 OK- Image details
{
"image_id": 1,
"product_id": 1,
"file_name": "product-image.jpg",
"date_created": "2025-11-04T10:40:00",
"s3_bucket_path": "users/1/products/1/uuid-product-image.jpg"
}404 Not Found- Product or image doesn't exist503 Service Unavailable- Database unreachable
Example:
curl -X GET http://localhost:8080/v1/product/1/image/1Delete an image (owner only).
Request:
- Authentication required (must be product owner)
- Query parameters allowed
Path Parameters:
productId: Product IDimageId: Image ID
Response:
204 No Content- Image deleted successfully (removed from both database and S3)401 Unauthorized- No authentication provided403 Forbidden- Not the product owner404 Not Found- Product or image doesn't exist503 Service Unavailable- Database or S3 unreachable
Example:
curl -X DELETE http://localhost:8080/v1/product/1/image/1 \
-u "johnny@email.com:Password123!"The application uses MySQL with JPA/Hibernate for ORM. Schema is automatically created and updated via spring.jpa.hibernate.ddl-auto=update.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ User │1 *│ Product │1 *│ Image │
│──────────────│───────│──────────────│───────│──────────────│
│ user_id (PK) │ │ id (PK) │ │ image_id(PK) │
│ email │ │ name │ │ product_id │
│ username │ │ description │ │ file_name │
│ password │ │ sku (UNIQUE) │ │ s3_bucket_ │
│ first_name │ │ manufacturer │ │ path │
│ last_name │ │ quantity │ │ date_created │
│ account_ │ │ date_added │ └──────────────┘
│ created │ │ date_last_ │
│ account_ │ │ updated │ ┌──────────────┐
│ updated │ │ owner_user_ │ │ HealthCheck │
└──────────────┘ │ id (FK) │ │──────────────│
└──────────────┘ │ check_id(PK) │
│ check_ │
│ datetime │
└──────────────┘
CREATE TABLE user (
user_id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
account_created DATETIME(6),
account_updated DATETIME(6)
);Constraints:
emailandusernameare unique (username must equal email)passwordis hashed with BCrypt- Timestamps auto-managed via JPA
@PrePersistand@PreUpdate
CREATE TABLE product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
sku VARCHAR(255) NOT NULL UNIQUE,
manufacturer VARCHAR(255) NOT NULL,
quantity INT NOT NULL CHECK (quantity >= 0),
date_added DATETIME(6),
date_last_updated DATETIME(6),
owner_user_id BIGINT NOT NULL,
FOREIGN KEY (owner_user_id) REFERENCES user(user_id) ON DELETE CASCADE
);Constraints:
skumust be unique across all productsquantitymust be >= 0owner_user_idis a foreign key touser.user_id- Cascading delete: Deleting a user deletes all their products
CREATE TABLE image (
image_id BIGINT AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT NOT NULL,
file_name VARCHAR(255) NOT NULL,
date_created DATETIME(6),
s3_bucket_path VARCHAR(500) NOT NULL,
FOREIGN KEY (product_id) REFERENCES product(id) ON DELETE CASCADE
);Constraints:
product_idis a foreign key toproduct.id- Cascading delete: Deleting a product deletes all associated images from database (application also deletes from S3)
s3_bucket_pathstores full path in S3 bucket
CREATE TABLE health_check (
check_id BIGINT AUTO_INCREMENT PRIMARY KEY,
check_datetime DATETIME(6) NOT NULL
);Purpose:
- Records each health check request to verify database connectivity
- Accumulates audit trail of health check requests
- Type: HTTP Basic Authentication
- Stateless: No sessions or cookies, credentials required with each request
- Username Format: Must be a valid email address
- Password Hashing: BCrypt with strength 10 (configured via
BCryptPasswordEncoderbean)
-
Public Endpoints (no auth required):
POST /v1/user- User registrationGET /v1/product/{id}- View productsGET /v1/product/{id}/image*- View product imagesGET /healthz- Health checks
-
Authenticated Endpoints:
- All user operations (GET, PUT)
- Product creation (POST)
- Product modifications (PATCH, PUT, DELETE)
- Image operations (POST, DELETE)
-
Ownership Validation:
- Users can only access/modify their own user data
- Users can only modify products they own (
owner_user_idcheck) - Product owners can manage product images
Enforced via @Pattern annotation on UserRequestDTO:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{}|;:',.<>?/~`]).{8,15}$
- 8-15 characters in length
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one digit (0-9)
- At least one special character from: `!@#$%^&*()_+-=[]{}|;:',.<>?/~``
From SecurityConfig.java:
- CSRF disabled (stateless API)
- Session management:
STATELESS - HTTP Basic authentication with
UserDetailsServiceintegration - Authorization rules defined via
SecurityFilterChain - All passwords stored as BCrypt hashes, never in plaintext
- Query Parameter Rejection: Most endpoints reject unexpected query parameters (400 Bad Request)
- Request Body Validation: GET and DELETE endpoints reject request bodies
- SQL Injection Prevention: JPA/Hibernate parameterized queries
- XSS Prevention:
X-Content-Type-Options: nosniffheader - Cache Control: Response headers prevent caching of sensitive data
- Read-Only Fields Protection: DTOs enforce that auto-generated fields cannot be set by users
The application is packaged as a custom Amazon Machine Image (AMI) using Packer.
Configuration File: aws-ubuntu.pkr.hcl
AMI Specifications:
- Base Image: Ubuntu 24.04 LTS Minimal (
ubuntu/images/hbm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*) - Instance Type: t2.micro
- Storage: 25GB GP2 EBS volume
- Region: Configurable via variables
Provisioning Steps (via scripts/setup.sh):
- Update system packages (
apt-get update && upgrade) - Install OpenJDK 21 JRE Headless
- Create system user
csye6225(non-login, system account) - Create application directory:
/opt/csye6225 - Copy application JAR to
/opt/csye6225/webapp.jar - Copy configuration file:
/opt/csye6225/application.properties - Install CloudWatch Agent (via
scripts/install-cloudwatch.sh) - Copy CloudWatch config:
/opt/cloudwatch-config.json - Copy systemd service file:
/etc/systemd/system/webapp.service - Create logs directory:
/opt/csye6225/logs - Set proper ownership (csye6225:csye6225) and permissions
- Enable systemd service (auto-start on boot)
Build Command:
packer build \
-var "aws_region=us-east-1" \
-var "subnet_id=subnet-xxxxx" \
aws-ubuntu.pkr.hclService File: webapp.service
[Unit]
Description=Webapp Spring Boot Application
After=network.target
[Service]
Type=simple
User=csye6225
Group=csye6225
WorkingDirectory=/opt/csye6225
EnvironmentFile=/opt/csye6225/app.env
ExecStart=/usr/bin/java -jar /opt/csye6225/webapp.jar
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.targetFeatures:
- Runs as non-root user (
csye6225) - Auto-restart on failure with 10-second delay
- Loads environment variables from
/opt/csye6225/app.env - Enabled on system boot
Management Commands:
sudo systemctl start webapp # Start application
sudo systemctl stop webapp # Stop application
sudo systemctl status webapp # Check status
sudo systemctl restart webapp # Restart application
sudo journalctl -u webapp -f # View logsConfiguration (in application.properties):
aws.region=${AWS_REGION:us-east-1}
aws.s3.bucket.name=${S3_BUCKET_NAME:}S3 Client Setup (S3Config.java):
- Uses AWS SDK v2
- Instance role-based authentication (no hardcoded credentials)
- Regional endpoint configuration
- Conditional bean creation (only if
S3_BUCKET_NAMEconfigured)
Storage Path Structure:
s3://bucket-name/
└── users/
└── {userId}/
└── products/
└── {productId}/
├── {uuid}-image1.jpg
├── {uuid}-image2.png
└── {uuid}-image3.jpeg
Operations:
- Upload:
PutObjectRequestwith automatic UUID prefix to prevent filename collisions - Delete:
DeleteObjectRequestwith full S3 key path - Metadata: Content-Type preserved from original upload
CloudWatch Agent Configuration (cloudwatch-config.json):
{
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/opt/csye6225/logs/application.log",
"log_group_name": "/aws/ec2/webapp",
"log_stream_name": "{instance_id}-application",
"timezone": "UTC"
}
]
}
}
},
"metrics": {
"namespace": "WebApp",
"metrics_collected": {
"statsd": {
"service_address": ":8125",
"metrics_collection_interval": 60,
"metrics_aggregation_interval": 60
}
}
}
}Features:
- Log Group:
/aws/ec2/webapp - Log Stream:
{instance_id}-application(unique per EC2 instance) - Metrics Namespace:
WebApp - StatsD Port: 8125 (UDP)
- Collection Interval: 60 seconds
Configuration (logback-spring.xml):
- Console Appender: For development (stdout)
- File Appender: For production (
/opt/csye6225/logs/application.log) - Format: JSON via Logstash Encoder
- Rolling Policy:
- Max file size: 10MB
- Max history: 7 days
- Total size cap: 100MB
- Compression:
.gz
Log Fields:
timestamp: ISO-8601 formatlevel: DEBUG, INFO, WARN, ERRORlogger: Fully qualified class namemessage: Log messagerequestId: Unique UUID per request (via MDC)thread: Thread nameexception: Stack trace if error
Example Log Entry:
{
"timestamp": "2025-11-04T10:50:32.123Z",
"level": "INFO",
"logger": "com.example.webapp.controller.ProductController",
"message": "Product created with ID: 1",
"requestId": "a3f8b2c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
"thread": "http-nio-8080-exec-1"
}Framework: Micrometer with StatsD export
Configuration (application.properties):
management.metrics.export.statsd.enabled=true
management.metrics.export.statsd.flavor=etsy
management.metrics.export.statsd.host=localhost
management.metrics.export.statsd.port=8125
management.metrics.export.statsd.protocol=udpMetrics Tracked (via MetricsService and MetricsFilter):
-
API Call Counts:
api.calls- Tagged with endpoint and method- Examples:
api.calls,endpoint=/v1/user,method=POST
-
Response Times:
api.response.time- Tagged with endpoint and method- Measured in milliseconds
-
Database Query Times:
db.query.time- Tagged with operation- Measured in milliseconds
-
S3 Operation Times:
s3.operation.time- Tagged with operation type (upload, delete)- Measured in milliseconds
-
Health Check Metrics:
health.check- Count of health check requestshealth.check.time- Response time
Metrics Format (StatsD):
api.calls,endpoint=/v1/product,method=POST:1|c
api.response.time,endpoint=/v1/product,method=POST:45|ms
db.query.time,operation=save:12|ms
s3.operation.time,operation=upload:230|ms
Implementation (RequestLoggingFilter):
- Generates unique UUID for each request
- Stored in MDC (Mapped Diagnostic Context) as
requestId - Automatically included in all log entries during request lifecycle
- Cleared after request completion
- Enables distributed tracing across logs
Usage:
- Request arrives → UUID generated → Added to MDC
- All log statements include
requestIdin JSON - Search CloudWatch Logs by
requestIdto trace full request flow
File: .github/workflows/test.yml
Triggers:
- Pull requests to
mainbranch
Jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: webapp
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- Checkout code
- Setup Java 21
- Create DB user with privileges
- Build with Maven (skip tests)
- Run all 160 tests
- Generate JaCoCo coverage report
- Upload coverage artifactsEnvironment:
- MySQL 8.0 service container
- Java 21
- Maven 3.x
Test Execution:
- Runs all integration and unit tests
- Validates 91% code coverage
- Blocks PR merge if tests fail (branch protection)
File: .github/workflows/packer-build.yml
Triggers:
- Push to
mainbranch (after PR merge)
Jobs:
packer-build:
runs-on: ubuntu-latest
steps:
- Checkout code
- Setup Java 21
- Build JAR with Maven (skip tests - already validated)
- Configure AWS credentials
- Setup Packer
- Initialize Packer
- Build AMI with custom configuration
- Share AMI with demo accountSecrets Required:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_REGIONSUBNET_IDDEMO_ACCOUNT_ID
Workflow Variables:
- Packer variables injected via
-varflags - AMI shared with specified AWS account for deployment
File: .github/workflows/packer-status-check.yml
Triggers:
- Pull requests to
mainbranch
Jobs:
packer-validate:
runs-on: ubuntu-latest
steps:
- Checkout code
- Setup Java 21
- Build JAR (skip tests)
- Setup Packer
- Run packer fmt -check (format validation)
- Run packer validate (template validation)Purpose:
- Validates Packer template syntax before merge
- Ensures AMI builds will succeed when merged to main
- Part of required status checks for PR approval
Main Branch Protection:
- ✅ Require pull request before merging
- ✅ Require status checks to pass:
- Integration tests (160 tests)
- Packer validation
- All checks must be up-to-date
- ✅ No direct pushes to main
- ✅ Require conversation resolution before merging
- ✅ Linear history enforced
Total Tests: 160
- Integration Tests: 95 tests across 3 test classes
- Unit Tests: 65 tests across 9 test classes
Coverage: src/main/java/com/example/webapp/controller/UserController.java
Test Categories:
- User registration validation (email format, password complexity, duplicate prevention)
- Authentication flows (Basic Auth, invalid credentials, missing credentials)
- Authorization checks (access control, forbidden resource access)
- User retrieval (successful GET, 403 for other users, 404 for non-existent)
- User updates (partial updates, password changes, validation errors)
- Query parameter rejection
- Request body validation on GET requests
- Read-only field protection
Key Test Methods:
testCreateUser_Success()
testCreateUser_InvalidEmail()
testCreateUser_WeakPassword()
testCreateUser_DuplicateEmail()
testGetUser_Success()
testGetUser_Unauthorized()
testGetUser_Forbidden()
testUpdateUser_Success()
testUpdateUser_PartialUpdate()
testUpdateUser_InvalidPassword()Coverage: src/main/java/com/example/webapp/controller/ProductController.java
Test Categories:
- Product creation (all fields, validation, SKU uniqueness)
- Product retrieval (public access, 404 handling)
- Partial updates (PATCH - individual fields, ownership validation)
- Full updates (PUT - all required fields, validation)
- Product deletion (ownership, cascading to images)
- Quantity validation (>= 0 constraint)
- SKU uniqueness enforcement
- Owner-based authorization
- Read-only field protection
- Query parameter rejection
Key Test Methods:
testCreateProduct_Success()
testCreateProduct_InvalidQuantity()
testCreateProduct_DuplicateSku()
testGetProduct_Success()
testGetProduct_NotFound()
testPatchProduct_Success()
testPatchProduct_Forbidden()
testPutProduct_Success()
testPutProduct_MissingFields()
testDeleteProduct_Success()
testDeleteProduct_Forbidden()Coverage: src/main/java/com/example/webapp/controller/HealthController.java
Test Categories:
- Successful health checks (200 OK)
- Database connectivity verification
- Query parameter rejection (400 Bad Request)
- Request body rejection (400 Bad Request)
- Unsupported HTTP methods (405 Method Not Allowed)
- Database unavailability handling (503 Service Unavailable)
- HealthCheck record creation
Key Test Methods:
testHealthCheck_Success()
testHealthCheck_WithQueryParams_Returns400()
testHealthCheck_WithRequestBody_Returns400()
testHealthCheck_PostMethod_Returns405()
testHealthCheck_DatabaseConnectivity()Entity Tests:
- User entity validation
- Product entity validation
- Image entity relationships
- Timestamp auto-generation
DTO Tests:
- UserRequestDTO validation rules
- UserResponseDTO field exclusions
- ProductRequestDTO constraints
- ImageResponseDTO mapping
Service Layer Tests:
- UserService business logic
- ProductService CRUD operations
- ImageService S3 integration (mocked)
- S3Service operations (mocked)
- MetricsService recording
Repository Tests:
- Custom query methods
- JPA findBy methods
- Unique constraint validation
Utility Tests:
- Password validation logic
- Email format validation
- SKU uniqueness checks
All Tests:
mvn testSpecific Test Class:
mvn test -Dtest=UserIntegrationTestSpecific Test Method:
mvn test -Dtest=UserIntegrationTest#testCreateUser_SuccessWith Coverage Report:
mvn clean test jacoco:reportView report: target/site/jacoco/index.html
Integration Tests Only:
mvn test -Dtest=*IntegrationTestUnit Tests Only:
mvn test -Dtest=*Test,!*IntegrationTestTest Profile (application-test.properties):
spring.datasource.url=jdbc:mysql://localhost:3306/webapp_test
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=false
aws.s3.bucket.name=test-bucketTest Annotations:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@TestMethodOrder(OrderAnnotation.class)
@Sql(executionPhase = BEFORE_TEST_METHOD, scripts = "classpath:cleanup.sql")Key Testing Libraries:
- REST Assured: Fluent HTTP API testing
- Mockito: Mocking S3 service, repositories
- AssertJ: Fluent assertions
- Spring Boot Test: Application context loading
- JUnit 5: Test framework with parameterized tests
Database Configuration:
DB_HOST=localhost # MySQL host (default: localhost)
DB_PORT=3306 # MySQL port (default: 3306)
DB_NAME=webapp # Database name (default: webapp)
DB_USER=csye6225 # Database user (default: csye6225)
DB_PASSWORD=your_password # Database password (required)AWS Configuration:
AWS_REGION=us-east-1 # AWS region (default: us-east-1)
S3_BUCKET_NAME=your-bucket # S3 bucket for images (required for image features)Application Configuration:
SPRING_PROFILES_ACTIVE=prod # Spring profile (default: none)
SERVER_PORT=8080 # Application port (default: 8080)File: src/main/resources/application.properties
Database Settings:
spring.application.name=webapp
spring.datasource.url=jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:webapp}
spring.datasource.username=${DB_USER:csye6225}
spring.datasource.password=${DB_PASSWORD:}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.DriverJPA/Hibernate Settings:
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=false
logging.level.org.hibernate.SQL=INFOServer Settings:
server.port=8080
server.error.whitelabel.enabled=falseAWS Settings:
aws.region=${AWS_REGION:us-east-1}
aws.s3.bucket.name=${S3_BUCKET_NAME:}Metrics Settings:
management.metrics.export.statsd.enabled=true
management.metrics.export.statsd.flavor=etsy
management.metrics.export.statsd.host=localhost
management.metrics.export.statsd.port=8125
management.metrics.export.statsd.protocol=udp
management.metrics.distribution.percentiles-histogram.http.server.requests=false
# Disable Spring Data repository metrics to prevent UNKNOWN values in CloudWatch
management.metrics.enable.data.repository.invocations=falseEnvironment File (on EC2): /opt/csye6225/app.env
DB_HOST=your-rds-endpoint.rds.amazonaws.com
DB_PORT=3306
DB_NAME=webapp
DB_USER=admin
DB_PASSWORD=your_secure_password
AWS_REGION=us-east-1
S3_BUCKET_NAME=your-production-bucket
SPRING_PROFILES_ACTIVE=prodLoaded by systemd service via:
EnvironmentFile=/opt/csye6225/app.env- Custom AMI built via Packer (see CI/CD Pipelines)
- AWS infrastructure provisioned via Terraform:
- VPC with 3 public and 3 private subnets across availability zones
- RDS MySQL instance (8.0+) in private subnets
- S3 bucket for image storage
- Application Load Balancer
- Auto Scaling Group (min: 3, max: 5 instances)
- Route53 hosted zone for domain
- IAM instance role with permissions:
s3:PutObject,s3:GetObject,s3:DeleteObjecton image bucketcloudwatch:PutMetricData,logs:CreateLogGroup,logs:CreateLogStream,logs:PutLogEvents
The application is deployed using AWS Auto Scaling Groups behind an Application Load Balancer:
- Route53 → Application Load Balancer → Auto Scaling Group (3-5 instances) → RDS/S3
- Instances are automatically launched/terminated based on CPU utilization
- Load balancer distributes traffic across healthy instances
- Access application via domain:
http://dev.yourdomain.tldorhttp://demo.yourdomain.tld
Note: Direct instance IP access is disabled. Application only accessible via load balancer.
The infrastructure is fully automated via Terraform. Manual deployment is NOT required.
1. Build Custom AMI (Automated via GitHub Actions):
# Triggered automatically on PR merge to main branch
# See .github/workflows/packer-build.yml
# Creates AMI with application pre-installed2. Deploy Infrastructure via Terraform:
cd tf-aws-infra-02
terraform init
terraform plan -var-file="dev.tfvars" # or demo.tfvars
terraform apply -var-file="dev.tfvars"3. Verify Deployment:
# Get load balancer DNS name
terraform output
# Test application via domain (wait 2-3 minutes for DNS propagation)
curl http://dev.yourdomain.tld/healthz
# Expected: 200 OK4. Monitor Auto Scaling:
# View Auto Scaling Group status
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names csye6225-asg
# View running instances
aws ec2 describe-instances --filters "Name=tag:Name,Values=*asg-instance*" --query 'Reservations[*].Instances[*].[InstanceId,State.Name,PrivateIpAddress]'Environment variables are automatically configured via Launch Template user data:
DB_HOST,DB_PORT,DB_NAME,DB_USER,DB_PASSWORD- From RDS instanceAWS_REGION- From Terraform variableS3_BUCKET_NAME- From S3 bucket resource
File location: /opt/csye6225/app.env (created automatically on instance launch)
For local development or testing, you can deploy a single EC2 instance manually. This approach is NOT recommended for production.
Launch EC2 Instance:
aws ec2 run-instances \
--image-id ami-xxxxx \
--instance-type t2.micro \
--key-name your-key-pair \
--security-group-ids sg-xxxxx \
--subnet-id subnet-xxxxx \
--iam-instance-profile Name=WebAppInstanceRole \
--user-data file://user-data.shConfigure and Start:
# SSH into instance
ssh -i your-key.pem ubuntu@instance-ip
# Environment variables are created by user-data script
# Verify application is running
sudo systemctl status webapp
curl http://localhost:8080/healthz- Name: csye6225-lb
- Scheme: Internet-facing
- Target Group: Port 8080, HTTP protocol
- Health Check Path:
/healthz - Health Check Interval: 30 seconds
- Healthy Threshold: 2 consecutive successes
- Unhealthy Threshold: 2 consecutive failures
- Timeout: 5 seconds
- Success Codes: 200
- Listener: HTTP port 80 → Forward to target group
- Name: csye6225-asg
- Launch Template: csye6225_asg (latest version)
- IAM instance profile: WebAppInstanceRole
- User data: Script to configure environment variables
- Security groups: Application security group
Auto Scaling Group:
- Min size: 3 instances (ensures high availability)
- Max size: 5 instances
- Desired capacity: 1 instance (scales up as needed)
- Cooldown period: 60 seconds
- Deployment: Spans 3 availability zones
- Health check type: ELB (via load balancer target group)
- Registered with: csye6225-lb-tg target group
Scaling Policies:
- Scale Up Policy:
- Trigger: Average CPU Utilization > 5%
- Action: Add 1 instance
- Cooldown: 60 seconds
- Scale Down Policy:
- Trigger: Average CPU Utilization < 3%
- Action: Remove 1 instance
- Cooldown: 60 seconds
CloudWatch Alarms:
csye6225-cpu-high: Monitors ASG average CPU > 5%csye6225-cpu-low: Monitors ASG average CPU < 3%
Initial Schema Setup: Schema is automatically created by Hibernate DDL on first run.
Manual Schema Inspection:
USE webapp;
SHOW TABLES;
DESCRIBE user;
DESCRIBE product;
DESCRIBE image;
DESCRIBE health_check;Backup Strategy:
# Backup
mysqldump -h your-rds-endpoint -u admin -p webapp > backup.sql
# Restore
mysql -h your-rds-endpoint -u admin -p webapp < backup.sqlCheck Application Logs:
# Systemd logs
sudo journalctl -u webapp -f
# Application log file
sudo tail -f /opt/csye6225/logs/application.logCheck CloudWatch Logs:
- Log Group:
/aws/ec2/webapp - Log Stream:
{instance-id}-application
Check CloudWatch Metrics:
- Namespace:
WebApp - Metrics:
api.calls,api.response.time,db.query.time,s3.operation.time
Verify S3 Bucket:
aws s3 ls s3://your-webapp-images-bucket/users/The application uses a centralized exception handler (GlobalExceptionHandler) that returns consistent error responses with appropriate HTTP status codes.
| Code | Meaning | When It Occurs |
|---|---|---|
| 200 OK | Success | Successful GET requests |
| 201 Created | Resource created | Successful POST for user, product, image |
| 204 No Content | Success, no body | Successful PUT, PATCH, DELETE |
| 400 Bad Request | Invalid input | Validation failures, malformed JSON, unexpected query params/body, duplicate SKU/email |
| 401 Unauthorized | Authentication required | Missing or invalid credentials |
| 403 Forbidden | Authorization failed | Attempting to access/modify another user's resources |
| 404 Not Found | Resource not found | User, product, or image doesn't exist |
| 405 Method Not Allowed | Unsupported method | Using POST on /healthz, etc. |
| 503 Service Unavailable | Service failure | Database connection lost, S3 unreachable |
All errors return JSON with consistent structure:
Example 400 Bad Request:
{
"timestamp": "2025-11-04T10:55:30.123Z",
"status": 400,
"error": "Bad Request",
"message": "Password must be 8-15 characters with uppercase, lowercase, digit, and special character",
"path": "/v1/user"
}Example 401 Unauthorized:
{
"timestamp": "2025-11-04T10:56:00.456Z",
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/v1/product"
}Example 403 Forbidden:
{
"timestamp": "2025-11-04T10:57:00.789Z",
"status": 403,
"error": "Forbidden",
"message": "You don't have permission to access this resource",
"path": "/v1/user/2"
}Missing Credentials:
curl -X GET http://localhost:8080/v1/user/1
# Response: 401 UnauthorizedInvalid Credentials:
curl -X GET http://localhost:8080/v1/user/1 \
-u "user@example.com:wrongpassword"
# Response: 401 UnauthorizedAccessing Another User's Data:
curl -X GET http://localhost:8080/v1/user/2 \
-u "user1@example.com:password123"
# Response: 403 Forbidden (if user1 has ID 1, not 2)Modifying Another User's Product:
curl -X DELETE http://localhost:8080/v1/product/5 \
-u "user1@example.com:password123"
# Response: 403 Forbidden (if product 5 is owned by user 2)Weak Password:
curl -X POST http://localhost:8080/v1/user \
-H "Content-Type: application/json" \
-d '{"username":"test@example.com","password":"weak","first_name":"Test","last_name":"User"}'
# Response: 400 Bad Request - Password validation failedNegative Quantity:
curl -X POST http://localhost:8080/v1/product \
-u "user@example.com:password" \
-H "Content-Type: application/json" \
-d '{"name":"Product","description":"Desc","sku":"SKU1","manufacturer":"Mfg","quantity":-5}'
# Response: 400 Bad Request - Quantity must be >= 0Duplicate SKU:
curl -X POST http://localhost:8080/v1/product \
-u "user@example.com:password" \
-H "Content-Type: application/json" \
-d '{"name":"Product","description":"Desc","sku":"EXISTING-SKU","manufacturer":"Mfg","quantity":10}'
# Response: 400 Bad Request - SKU already existsUnexpected Query Parameters:
curl -X GET "http://localhost:8080/healthz?debug=true"
# Response: 400 Bad Request - Query parameters not allowed
curl -X GET "http://localhost:8080/v1/product/1?details=full"
# Response: 400 Bad Request - Query parameters not allowedRequest Body on GET:
curl -X GET http://localhost:8080/v1/user/1 \
-u "user@example.com:password" \
-H "Content-Type: application/json" \
-d '{"extra":"data"}'
# Response: 400 Bad Request - Request body not allowedDatabase Down:
# If MySQL is unavailable
curl -X GET http://localhost:8080/healthz
# Response: 503 Service Unavailable
curl -X POST http://localhost:8080/v1/user \
-H "Content-Type: application/json" \
-d '{"username":"test@example.com","password":"Password123!","first_name":"Test","last_name":"User"}'
# Response: 503 Service Unavailable - Database connection errorS3 Upload Failure:
# If S3 bucket doesn't exist or permissions are wrong
curl -X POST http://localhost:8080/v1/product/1/image \
-u "owner@example.com:password" \
-F "file=@image.jpg"
# Response: 503 Service Unavailable - Failed to upload image to S3All error responses include security headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
X-Content-Type-Options: nosniff
webapp-02/
├── src/
│ ├── main/
│ │ ├── java/com/example/webapp/
│ │ │ ├── WebappApplication.java # Main entry point
│ │ │ ├── config/
│ │ │ │ ├── AppConfig.java # BCrypt bean
│ │ │ │ ├── S3Config.java # AWS S3 client config
│ │ │ │ └── SecurityConfig.java # Spring Security config
│ │ │ ├── controller/
│ │ │ │ ├── HealthController.java # /healthz endpoint
│ │ │ │ ├── UserController.java # /v1/user endpoints
│ │ │ │ ├── ProductController.java # /v1/product endpoints
│ │ │ │ └── ImageController.java # /v1/product/{id}/image endpoints
│ │ │ ├── entity/
│ │ │ │ ├── User.java # User JPA entity
│ │ │ │ ├── Product.java # Product JPA entity
│ │ │ │ ├── Image.java # Image JPA entity
│ │ │ │ └── HealthCheck.java # HealthCheck JPA entity
│ │ │ ├── dto/
│ │ │ │ ├── UserRequestDTO.java # User input DTO
│ │ │ │ ├── UserResponseDTO.java # User response DTO
│ │ │ │ ├── ProductRequestDTO.java # Product input DTO
│ │ │ │ ├── ProductResponseDTO.java # Product response DTO
│ │ │ │ └── ImageResponseDTO.java # Image response DTO
│ │ │ ├── service/
│ │ │ │ ├── UserService.java # User business logic + UserDetailsService
│ │ │ │ ├── ProductService.java # Product business logic
│ │ │ │ ├── ImageService.java # Image management logic
│ │ │ │ ├── S3Service.java # AWS S3 operations
│ │ │ │ ├── HealthCheckService.java # Health check logic
│ │ │ │ └── MetricsService.java # Metrics recording
│ │ │ ├── repository/
│ │ │ │ ├── UserRepository.java # User data access
│ │ │ │ ├── ProductRepository.java # Product data access
│ │ │ │ ├── ImageRepository.java # Image data access
│ │ │ │ └── HealthCheckRepository.java # HealthCheck data access
│ │ │ ├── filter/
│ │ │ │ ├── MetricsFilter.java # API metrics collection
│ │ │ │ └── RequestLoggingFilter.java # Request ID tracking
│ │ │ └── exception/
│ │ │ └── GlobalExceptionHandler.java # Centralized error handling
│ │ └── resources/
│ │ ├── application.properties # Application configuration
│ │ └── logback-spring.xml # Logging configuration
│ └── test/
│ └── java/com/example/webapp/
│ ├── UserIntegrationTest.java # User endpoint tests (34 tests)
│ ├── ProductIntegrationTest.java # Product endpoint tests (49 tests)
│ ├── HealthCheckIntegrationTest.java # Health endpoint tests (12 tests)
│ └── [9 more unit test files] # Unit tests (65 tests)
├── scripts/
│ ├── setup.sh # Main deployment script
│ └── install-cloudwatch.sh # CloudWatch agent installer
├── .github/
│ └── workflows/
│ ├── test.yml # Integration tests workflow
│ ├── packer-build.yml # AMI build workflow
│ └── packer-status-check.yml # Packer validation workflow
├── aws-ubuntu.pkr.hcl # Packer AMI template
├── cloudwatch-config.json # CloudWatch agent config
├── webapp.service # Systemd service file
├── pom.xml # Maven configuration
├── README.md # This file
├── HELP.md # Spring Boot help
└── .gitignore # Git ignore rules
WebappApplication.java: Main Spring Boot application class with@SpringBootApplication
application.properties: Database, AWS, metrics, and JPA configurationlogback-spring.xml: Logging configuration with Logstash encoderSecurityConfig.java: HTTP Basic Auth, authorization rules, stateless sessionS3Config.java: AWS S3 client bean configurationAppConfig.java: BCrypt password encoder bean
aws-ubuntu.pkr.hcl: Packer template for AMI buildscloudwatch-config.json: CloudWatch Agent configuration for logs and metricswebapp.service: Systemd service definitionsetup.sh: Main provisioning script (installs Java, creates user, deploys app)install-cloudwatch.sh: CloudWatch Agent installation script
pom.xml: Maven POM with dependencies, plugins, and build configuration
.github/workflows/test.yml: Runs 160 tests on PR.github/workflows/packer-build.yml: Builds AMI on merge to main.github/workflows/packer-status-check.yml: Validates Packer template on PR
From pom.xml:
Spring Boot Starters:
spring-boot-starter-web- REST API frameworkspring-boot-starter-data-jpa- JPA/Hibernate ORMspring-boot-starter-security- Authentication/authorizationspring-boot-starter-validation- Input validationspring-boot-starter-actuator- Metrics and health endpoints
Database:
mysql-connector-j- MySQL JDBC driver
Cloud:
software.amazon.awssdk:s3- AWS SDK v2 for S3
Monitoring:
micrometer-registry-statsd- Metrics export to StatsDlogstash-logback-encoder- JSON logging for CloudWatch
Testing:
spring-boot-starter-test- Test frameworkrest-assured- HTTP API testingjacoco-maven-plugin- Code coverage
This webapp is a production-ready cloud-native application demonstrating:
- Modern Architecture: RESTful API with Spring Boot 3.x and Java 21
- Security Best Practices: BCrypt hashing, stateless auth, ownership-based authorization
- Cloud Integration: AWS S3 storage, CloudWatch monitoring, Packer AMI builds
- High Quality: 91% test coverage with comprehensive integration tests
- DevOps Excellence: CI/CD pipelines, automated testing, infrastructure as code
- Observability: Structured logging, metrics export, request tracing
- Scalability: Stateless design, load balancer ready, horizontal scaling support
Arundhati Bandopadhyaya NUID: 002313855 Northeastern University-- Network Structures & Cloud Computing Course
This project is part of an academic course at Northeastern University.