Skip to content

Repository files navigation

Object Store Application

A Java Spring Boot application that acts as an object store for text data, with REST API endpoints for reading and writing data to a PostgreSQL database.

Quick Start:

Features

  • RESTful API for storing and retrieving text objects
  • PostgreSQL database backend
  • Automatic database schema creation
  • Unique key-based storage with automatic updates
  • Timestamp tracking (created_at, updated_at)
  • Prometheus metrics for monitoring (endpoint latency, request counts, business metrics)
  • Comprehensive logging with configurable log levels

Prerequisites

For Local Development

  • Java 11 or higher
  • Maven 3.6+
  • PostgreSQL 14+ (or compatible version)
  • macOS, Linux, or Windows

For Docker Deployment

See DOCKER.md for Docker prerequisites and setup instructions.

Database Setup

Installing PostgreSQL

macOS (using Homebrew)

# Install PostgreSQL 14
brew install postgresql@14

# Start PostgreSQL service (will auto-start on login)
brew services start postgresql@14

Linux (Ubuntu/Debian)

# Update package list
sudo apt update

# Install PostgreSQL
sudo apt install postgresql postgresql-contrib

# Start PostgreSQL service
sudo systemctl start postgresql
sudo systemctl enable postgresql

Windows

Download and install PostgreSQL from https://www.postgresql.org/download/windows/

Creating the Database

  1. Connect to PostgreSQL (as your default user):

    # macOS (Homebrew)
    /opt/homebrew/opt/postgresql@14/bin/psql -d postgres
    
    # Linux
    sudo -u postgres psql
    
    # Windows (use psql from PostgreSQL installation)
    psql -U postgres
  2. Create the database:

    CREATE DATABASE objectstore;
  3. Create the user (if not using default postgres user):

    CREATE USER postgres WITH PASSWORD 'postgres' SUPERUSER;
    GRANT ALL PRIVILEGES ON DATABASE objectstore TO postgres;
  4. Verify the database:

    \l

    You should see objectstore in the list.

Database Configuration

The application is configured to connect to PostgreSQL with the following default settings (in src/main/resources/application.properties):

spring.datasource.url=jdbc:postgresql://localhost:5432/objectstore
spring.datasource.username=postgres
spring.datasource.password=postgres

To change these settings, edit src/main/resources/application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password

Running the Application

Option 1: Using Maven (Recommended for Development)

  1. Navigate to the project directory:

    cd /path/to/MercorProj
  2. Ensure PostgreSQL is running:

    # macOS (Homebrew)
    brew services list | grep postgresql@14
    
    # Linux
    sudo systemctl status postgresql
  3. Run the application:

    mvn spring-boot:run

    The application will start on http://localhost:8080

Option 2: Using the JAR File

  1. Build the application:

    mvn clean package
  2. Run the JAR:

    java -jar target/MercorProj-1.0-SNAPSHOT.jar

Option 3: Using IDE

  1. Open the project in your IDE (IntelliJ IDEA, Eclipse, etc.)
  2. Run the ObjectStoreApplication class
  3. The application will start automatically

Option 4: Using Docker

For Docker deployment instructions, including Docker Compose setup, building images, running containers, environment variables, and all Docker-related commands, see DOCKER.md.

Configuring Log Levels

The application supports different log levels (ERROR, WARN, INFO, DEBUG, TRACE). You can configure logging in several ways:

Method 1: Environment Variable

Set the LOG_LEVEL environment variable before starting the application:

# Set log level to DEBUG
export LOG_LEVEL=DEBUG
mvn spring-boot:run

# Or set it inline
LOG_LEVEL=DEBUG mvn spring-boot:run

# Set log level to ERROR (less verbose)
LOG_LEVEL=ERROR mvn spring-boot:run

Method 2: Command Line Argument

Pass the log level as a system property:

# Using Maven
mvn spring-boot:run -Dlogging.level.root=DEBUG

# Using JAR
java -jar target/MercorProj-1.0-SNAPSHOT.jar --logging.level.root=DEBUG

# Set application-specific log level
java -jar target/MercorProj-1.0-SNAPSHOT.jar --logging.level.com.mercor.objectstore=DEBUG

Method 3: Application Properties

Edit src/main/resources/application.properties:

# Set root log level
logging.level.root=DEBUG

# Set application-specific log level
logging.level.com.mercor.objectstore=DEBUG

Available Log Levels

  • ERROR: Only error messages (highest priority)
  • WARN: Warning and error messages
  • INFO: Informational, warning, and error messages (default)
  • DEBUG: Detailed debugging information
  • TRACE: Very detailed tracing information (most verbose)

Log Level Examples

# Production mode (minimal logging)
LOG_LEVEL=WARN mvn spring-boot:run

# Development mode (detailed logging)
LOG_LEVEL=DEBUG mvn spring-boot:run

# Troubleshooting mode (very detailed)
LOG_LEVEL=TRACE mvn spring-boot:run

Viewing Logs

Logs are output to the console by default. The log format includes:

  • Timestamp
  • Thread name
  • Log level
  • Logger name
  • Message

Example log output:

2026-01-02 15:55:32.123 [main] INFO  c.m.o.ObjectStoreApplication - Starting Object Store Application...
2026-01-02 15:55:32.456 [main] INFO  c.m.o.c.TextObjectController - TextObjectController initialized
2026-01-02 15:55:33.789 [http-nio-8080-exec-1] INFO  c.m.o.c.TextObjectController - Creating or updating object with key=test-key

Verifying the Application

Once the application is running, you should see output similar to:

Started ObjectStoreApplication in X.XXX seconds
Tomcat started on port(s): 8080 (http)

The database table text_objects will be automatically created on first startup.

Running Tests

The project includes comprehensive unit tests for both the service and controller layers. The tests use JUnit 5, Mockito, and Spring Boot Test.

Running All Tests

To run all tests in the project:

mvn test

This will:

  • Compile the test code
  • Run all unit tests
  • Display a summary of test results

Expected output:

Tests run: 24, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Running Specific Test Classes

You can run individual test classes:

# Run service layer tests only
mvn test -Dtest=TextObjectServiceTest

# Run controller layer tests only
mvn test -Dtest=TextObjectControllerTest

Running Specific Test Methods

To run a specific test method within a test class:

mvn test -Dtest=TextObjectServiceTest#testSave_NewObject

Test Coverage

The project includes the following test suites:

TextObjectServiceTest (10 tests)

  • Tests for creating new objects
  • Tests for updating existing objects
  • Tests for retrieving objects by key
  • Tests for listing all objects
  • Tests for checking object existence
  • Tests for deleting objects
  • Edge cases (empty keys, null content)

TextObjectControllerTest (14 tests)

  • REST API endpoint tests using MockMvc
  • Request validation tests
  • Success and error response tests
  • HTTP status code verification
  • JSON response validation

Running Tests in IDE

Most IDEs provide built-in test runners:

IntelliJ IDEA:

  1. Right-click on a test class or method
  2. Select "Run 'TestName'"
  3. Or use the green play button next to test methods

Eclipse:

  1. Right-click on a test class
  2. Select "Run As" → "JUnit Test"

Test Output

When tests run successfully, you'll see output like:

[INFO] Running com.mercor.objectstore.service.TextObjectServiceTest
[INFO] Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
[INFO] Running com.mercor.objectstore.controller.TextObjectControllerTest
[INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] Results:
[INFO] Tests run: 24, Failures: 0, Errors: 0, Skipped: 0

Skipping Tests During Build

If you need to skip tests during the build process:

# Skip tests when packaging
mvn clean package -DskipTests

# Skip tests and compilation
mvn clean package -Dmaven.test.skip=true

Note: It's recommended to run tests before committing code to ensure everything works correctly.

API Endpoints

Base URL

http://localhost:8080/api/objects

Health Check Endpoints

The service provides multiple health check endpoints for monitoring and status verification.

1. Custom Health Check (with Database Status)

GET /health

Returns a comprehensive health status including database connectivity.

Response (200 OK when healthy):

{
  "status": "UP",
  "service": "Object Store API",
  "database": "UP"
}

Response (503 Service Unavailable when unhealthy):

{
  "status": "DOWN",
  "service": "Object Store API",
  "database": "DOWN",
  "databaseError": "Connection refused"
}

Example using curl:

curl http://localhost:8080/health

2. Simple Health Check

GET /health/simple

Returns a simple status check without database verification.

Response (200 OK):

{
  "status": "UP"
}

Example using curl:

curl http://localhost:8080/health/simple

3. Spring Boot Actuator Health

GET /actuator/health

Spring Boot Actuator's built-in health endpoint.

Response (200 OK):

{
  "status": "UP"
}

Example using curl:

curl http://localhost:8080/actuator/health

Note: The health check endpoints are useful for:

  • Monitoring tools and load balancers
  • Kubernetes liveness/readiness probes
  • CI/CD pipeline health checks
  • Quick service status verification

4. Prometheus Metrics

GET /actuator/prometheus

Exposes application metrics in Prometheus format for monitoring and alerting.

Response (200 OK):

# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{application="objectstore",method="POST",status="200",uri="/api/objects"} 5.0

# HELP http_request_duration_seconds HTTP request latency
# TYPE http_request_duration_seconds summary
http_request_duration_seconds{application="objectstore",quantile="0.5"} 0.023
http_request_duration_seconds{application="objectstore",quantile="0.95"} 0.045
http_request_duration_seconds{application="objectstore",quantile="0.99"} 0.089

# HELP objectstore_objects_created_total Total number of objects created
# TYPE objectstore_objects_created_total counter
objectstore_objects_created_total{application="objectstore"} 10.0

Example using curl:

curl http://localhost:8080/actuator/prometheus

Available Metrics:

  • HTTP Metrics:

    • http_requests_total - Total number of HTTP requests (tagged by method, URI, status)
    • http_request_duration_seconds - Request latency (with percentiles: 0.5, 0.95, 0.99)
    • http_requests_errors - Total number of HTTP errors (4xx, 5xx)
  • Business Metrics:

    • objectstore_objects_created_total - Total objects created
    • objectstore_objects_updated_total - Total objects updated
    • objectstore_objects_deleted_total - Total objects deleted
    • objectstore_objects_retrieved_total - Total objects retrieved
  • Endpoint Metrics:

    • objectstore_api_seconds - API endpoint latency (tagged by endpoint)
    • objectstore_api_create_or_update_seconds - Create/update endpoint latency
    • objectstore_api_get_by_key_seconds - Get by key endpoint latency
    • objectstore_api_get_all_seconds - Get all endpoint latency
    • objectstore_api_delete_seconds - Delete endpoint latency

Prometheus Configuration:

The project includes a prometheus.yml configuration file that's ready to use. When running with Docker Compose, Prometheus is automatically configured to scrape metrics from the application.

Running Prometheus Locally

Option 1: Using Docker Compose (Recommended)

For Docker Compose setup, see DOCKER.md. Prometheus is included in the docker-compose.yml file and will start automatically when you run docker-compose up -d.

Prometheus UI will be available at: http://localhost:9090

Option 2: Running Prometheus Standalone

If you want to run Prometheus separately (without Docker Compose):

  1. Download Prometheus:

    # macOS (using Homebrew)
    brew install prometheus
    
    # Or download from https://prometheus.io/download/
  2. Run Prometheus:

    # Navigate to project directory
    cd /path/to/MercorProj
    
    # Run Prometheus with the included config
    prometheus --config.file=prometheus.yml --storage.tsdb.path=./prometheus-data
  3. Access Prometheus UI:

Option 3: Using Docker (Standalone)

For Docker setup, see DOCKER.md.

Using Prometheus UI

Once Prometheus is running, you can query metrics using the Prometheus Query Language (PromQL):

  1. Open Prometheus UI: http://localhost:9090

  2. Example Queries:

    Request Rate (requests per second):

    rate(http_requests_total[5m])
    

    Request Latency (95th percentile):

    histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
    

    Total Objects Created:

    objectstore_objects_created_total
    

    Error Rate:

    rate(http_requests_errors[5m])
    

    API Endpoint Latency:

    rate(objectstore_api_seconds_sum[5m]) / rate(objectstore_api_seconds_count[5m])
    

    Request Count by Method:

    sum by (method) (rate(http_requests_total[5m]))
    

    Request Count by Status:

    sum by (status) (http_requests_total)
    
  3. Viewing Targets:

    • Navigate to Status → Targets to verify the application is being scraped
    • The objectstore target should show as UP
  4. Graphing Metrics:

    • Enter a query in the "Expression" field
    • Click Execute
    • Switch to Graph tab to see the visualization
    • Use the time range selector to adjust the time window

Prometheus Alerting

The project includes Prometheus Alertmanager for email notifications. Alerts are configured in alert_rules.yml and sent via email.

Setup Email Alerts
  1. Configure Gmail App Password (see ALERT_SETUP.md for detailed instructions):

  2. Start Services:

    • For Docker Compose setup, see DOCKER.md
    • Or run Prometheus and Alertmanager manually
  3. Access Alertmanager UI: http://localhost:9093

Test Alert

A test alert is configured that fires immediately to verify email notifications:

  • Alert Name: TestAlert
  • Purpose: Verify alerting is working
  • Email Recipient: samgearou@gmail.com
  • Fires: Immediately when Prometheus starts
Available Alerts
  • TestAlert: Fires immediately (for testing email setup)
  • HighErrorRate: Error rate > 5% for 2 minutes
  • CriticalErrorRate: Error rate > 20% for 1 minute
  • HighLatency: 95th percentile latency > 1s for 5 minutes
  • CriticalLatency: 95th percentile latency > 5s for 2 minutes
  • ServiceDown: Service unavailable for 1 minute
  • LowRequestRate: Request rate < 0.01 req/s for 10 minutes
  • HighMemoryUsage: JVM heap > 90% for 5 minutes
Viewing Alerts

For more details, see ALERT_SETUP.md

Prometheus Configuration File

The prometheus.yml file includes:

  • Scrape interval: 15 seconds (10 seconds for application metrics)
  • Retention: 30 days (when using Docker Compose)
  • Targets:
    • Prometheus self-monitoring (localhost:9090)
    • Object Store Application (app:8080)

You can customize the configuration by editing prometheus.yml.

5. Metrics Endpoint

GET /actuator/metrics

Lists all available metric names.

Example using curl:

# List all metrics
curl http://localhost:8080/actuator/metrics

# Get specific metric
curl http://localhost:8080/actuator/metrics/http.requests.total

Example curl Requests

Here are practical curl examples you can use to interact with the API. Make sure the application is running on http://localhost:8080 before executing these commands.

0. Check Service Health

# Comprehensive health check (includes database status)
curl http://localhost:8080/healthhttp://localhost:8080/health

# Simple health check
curl http://localhost:8080/health/simple

# Spring Boot Actuator health check
curl http://localhost:8080/actuator/health

Expected Response:

{
  "status": "UP",
  "service": "Object Store API",
  "database": "UP"
}

1. Create a New Text Object

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "my-first-key", "content": "This is my first text object"}'

Expected Response:

{
  "id": 1,
  "key": "my-first-key",
  "content": "This is my first text object",
  "createdAt": "2026-01-02T15:41:43.123456",
  "updatedAt": null
}

2. Create Another Text Object

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "my-second-key", "content": "This is my second text object with different content"}'

3. Get a Text Object by Key

curl http://localhost:8080/api/objects/my-first-key

Expected Response:

{
  "id": 1,
  "key": "my-first-key",
  "content": "This is my first text object",
  "createdAt": "2026-01-02T15:41:43.123456",
  "updatedAt": null
}

4. Get All Text Objects

curl http://localhost:8080/api/objects

Expected Response:

[
  {
    "id": 1,
    "key": "my-first-key",
    "content": "This is my first text object",
    "createdAt": "2026-01-02T15:41:43.123456",
    "updatedAt": null
  },
  {
    "id": 2,
    "key": "my-second-key",
    "content": "This is my second text object with different content",
    "createdAt": "2026-01-02T15:42:00.123456",
    "updatedAt": null
  }
]

5. Update an Existing Text Object

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "my-first-key", "content": "This is the updated content for my first key"}'

Expected Response:

{
  "id": 1,
  "key": "my-first-key",
  "content": "This is the updated content for my first key",
  "createdAt": "2026-01-02T15:41:43.123456",
  "updatedAt": "2026-01-02T15:45:00.123456"
}

6. Delete a Text Object

curl -X DELETE http://localhost:8080/api/objects/my-second-key

Expected Response: HTTP 204 No Content (empty response body)

7. Try to Get a Non-Existent Object

curl -v http://localhost:8080/api/objects/non-existent-key

Expected Response: HTTP 404 Not Found

8. Try to Delete a Non-Existent Object

curl -v -X DELETE http://localhost:8080/api/objects/non-existent-key

Expected Response: HTTP 404 Not Found

9. Create Object with Special Characters

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "special-chars-key", "content": "Content with special chars: !@#$%^&*() and newlines\nand tabs\t"}'

10. Create Object with JSON Content

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "json-content-key", "content": "{\"name\": \"John\", \"age\": 30}"}'

11. Pretty Print JSON Responses

To get formatted JSON output, pipe the response through jq (if installed):

curl http://localhost:8080/api/objects | jq

Or use Python for formatting:

curl http://localhost:8080/api/objects | python -m json.tool

12. Save Response to File

curl http://localhost:8080/api/objects -o response.json

13. Include Response Headers

curl -i http://localhost:8080/api/objects

14. Verbose Output (for debugging)

curl -v -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "debug-key", "content": "Debug content"}'

15. Test with Invalid Request (Missing Key)

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"content": "Missing key field"}'

Expected Response: HTTP 400 Bad Request

16. Test with Invalid Request (Empty Key)

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "", "content": "Empty key"}'

Expected Response: HTTP 400 Bad Request

Quick Test Sequence

Run this sequence to test all operations:

# 1. Create an object
curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "test-key", "content": "Initial content"}'

# 2. Retrieve it
curl http://localhost:8080/api/objects/test-key

# 3. Update it
curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "test-key", "content": "Updated content"}'

# 4. Get all objects
curl http://localhost:8080/api/objects

# 5. Delete it
curl -X DELETE http://localhost:8080/api/objects/test-key

# 6. Verify it's deleted (should return 404)
curl -v http://localhost:8080/api/objects/test-key

1. Create or Update a Text Object

POST /api/objects

Creates a new text object or updates an existing one if the key already exists.

Request Body:

{
  "key": "my-unique-key",
  "content": "This is the text content to store"
}

Response (200 OK):

{
  "id": 1,
  "key": "my-unique-key",
  "content": "This is the text content to store",
  "createdAt": "2026-01-02T15:41:43",
  "updatedAt": null
}

Example using curl:

curl -X POST http://localhost:8080/api/objects \
  -H "Content-Type: application/json" \
  -d '{"key": "example-key", "content": "Hello, World!"}'

2. Get a Text Object by Key

curl -X GET http://localhost:8080/api/objects/example-key \                                          03:49:53 PM
  -H "Content-Type: application/json"

Retrieves a text object by its unique key.

Response (200 OK):

{
  "id": 1,
  "key": "example-key",
  "content": "Hello, World!",
  "createdAt": "2026-01-02T15:41:43",
  "updatedAt": null
}

Response (404 Not Found) if key doesn't exist:

(empty response)

Example using curl:

curl http://localhost:8080/api/objects/example-key

3. Get All Text Objects

curl -X GET http://localhost:8080/api/objects \                                                      03:49:53 PM
  -H "Content-Type: application/json"

Retrieves all text objects stored in the database.

Response (200 OK):

[
  {
    "id": 1,
    "key": "example-key",
    "content": "Hello, World!",
    "createdAt": "2026-01-02T15:41:43",
    "updatedAt": null
  },
  {
    "id": 2,
    "key": "another-key",
    "content": "Another text object",
    "createdAt": "2026-01-02T15:42:00",
    "updatedAt": "2026-01-02T15:43:00"
  }
]

Example using curl:

curl http://localhost:8080/api/objects

4. Delete a Text Object

DELETE /api/objects/{key}

Deletes a text object by its unique key.

Response (204 No Content) if successful

Response (404 Not Found) if key doesn't exist

Example using curl:

curl -X DELETE http://localhost:8080/api/objects/example-key

Database Management

Viewing Data Directly

Connect to the database:

# macOS (Homebrew)
PGPASSWORD=postgres /opt/homebrew/opt/postgresql@14/bin/psql -U postgres -d objectstore

# Linux
sudo -u postgres psql -d objectstore

Then run SQL queries:

-- View all objects
SELECT * FROM text_objects;

-- View specific object
SELECT * FROM text_objects WHERE key = 'example-key';

-- Count objects
SELECT COUNT(*) FROM text_objects;

Managing PostgreSQL Service

macOS (Homebrew)

# Start PostgreSQL
brew services start postgresql@14

# Stop PostgreSQL
brew services stop postgresql@14

# Restart PostgreSQL
brew services restart postgresql@14

# Check status
brew services list | grep postgresql@14

Linux

# Start PostgreSQL
sudo systemctl start postgresql

# Stop PostgreSQL
sudo systemctl stop postgresql

# Restart PostgreSQL
sudo systemctl restart postgresql

# Check status
sudo systemctl status postgresql

# Enable auto-start on boot
sudo systemctl enable postgresql

Troubleshooting

Application won't start

  1. Check if PostgreSQL is running:

    # macOS
    brew services list | grep postgresql
    
    # Linux
    sudo systemctl status postgresql
  2. Verify database connection:

    # Test connection
    PGPASSWORD=postgres psql -U postgres -d objectstore -c "SELECT 1;"
  3. Check application logs for specific error messages

Connection refused error

  • Ensure PostgreSQL is running on port 5432
  • Verify database credentials in application.properties
  • Check if PostgreSQL is listening: lsof -i :5432 (macOS/Linux)

Port 8080 already in use

Change the port in application.properties:

server.port=8081

Database doesn't exist

Create it manually:

CREATE DATABASE objectstore;

Project Structure

MercorProj/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/mercor/objectstore/
│   │   │       ├── ObjectStoreApplication.java    # Main application class
│   │   │       ├── controller/
│   │   │       │   └── TextObjectController.java  # REST API endpoints
│   │   │       ├── model/
│   │   │       │   └── TextObject.java            # Entity model
│   │   │       ├── repository/
│   │   │       │   └── TextObjectRepository.java   # Data access layer
│   │   │       └── service/
│   │   │           └── TextObjectService.java      # Business logic
│   │   └── resources/
│   │       └── application.properties              # Configuration
│   └── test/
│       └── java/
│           └── com/mercor/objectstore/
│               ├── controller/
│               │   └── TextObjectControllerTest.java  # Controller unit tests
│               └── service/
│                   └── TextObjectServiceTest.java      # Service unit tests
├── target/                                         # Build output
└── pom.xml                                         # Maven configuration

Technology Stack

  • Java 11
  • Spring Boot 2.7.14
  • Spring Data JPA
  • Hibernate
  • PostgreSQL 14+
  • Maven

License

This project is provided as-is for demonstration purposes.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages