Skip to main content

Developer Guide


Module: Lens Version: 1.0.0-RELEASE Last Updated: October 25, 2025


Overview

This guide helps developers get started with the AWS Lens module:

  • Environment setup
  • Building and running locally
  • Testing strategies
  • Code style and conventions
  • Contributing guidelines
  • Debugging tips
  • Common development tasks

1. Getting Started

1.1 Prerequisites

Required Software:

  • Java 17+ (OpenJDK recommended)
  • Gradle 7.6+
  • Git
  • Docker & Docker Compose
  • IDE (IntelliJ IDEA recommended)

Optional Tools:

  • Postman (API testing)
  • DBeaver (database client)
  • Redis Desktop Manager
  • RabbitMQ Management Plugin

1.2 Installation

Install Java 17:

# Ubuntu/Debian
sudo apt update
sudo apt install openjdk-17-jdk

# macOS (Homebrew)
brew install openjdk@17

# Verify installation
java -version

Install Gradle:

# Ubuntu/Debian
sudo apt install gradle

# macOS (Homebrew)
brew install gradle

# Verify installation
gradle --version

Install Docker:

# Ubuntu
sudo apt install docker.io docker-compose

# macOS
brew install --cask docker

# Verify installation
docker --version
docker-compose --version

2. Project Setup

2.1 Clone Repository

# Clone the repository
git clone https://github.com/cloudkeeper/lens.git
cd lens

# Create feature branch
git checkout -b feature/my-feature

2.2 Environment Configuration

Create .env file (for local development):

# Database connections
SNOWFLAKE_URL=jdbc:snowflake://dev-account.snowflakecomputing.com
SNOWFLAKE_USER=dev_user
SNOWFLAKE_PASSWORD=dev_password

# MongoDB
MONGODB_HOST=localhost
MONGODB_USER=admin
MONGODB_PASSWORD=password

# MySQL
MYSQL_HOST=localhost
MYSQL_USER=lensuser
MYSQL_PASSWORD=lenspassword

# Redis
REDIS_HOST=localhost
REDIS_PASSWORD=

# RabbitMQ
RABBITMQ_HOST=localhost
RABBITMQ_USER=admin
RABBITMQ_PASSWORD=password

# Config Server
CONFIG_SERVER_URI=http://localhost:8888

# JWT
JWT_SECRET=my-secret-key-for-development-only

Important: Never commit .env files to version control. Add to .gitignore:

echo ".env" >> .gitignore

2.3 Start Local Dependencies

# Start all dependencies with Docker Compose
docker-compose -f docker-compose-dev.yml up -d

# Verify services are running
docker-compose -f docker-compose-dev.yml ps

# View logs
docker-compose -f docker-compose-dev.yml logs -f

docker-compose-dev.yml (dependencies only):

version: '3.8'

services:
mongodb:
image: mongo:5.0
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password

mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: lens
MYSQL_USER: lensuser
MYSQL_PASSWORD: lenspassword

redis:
image: redis:6.2-alpine
ports:
- "6379:6379"

rabbitmq:
image: rabbitmq:3.9-management-alpine
ports:
- "5672:5672"
- "15672:15672"
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: password

3. Building the Project

3.1 Gradle Commands

# Clean build
gradle clean

# Compile without tests
gradle build -x test

# Compile with tests
gradle build

# Run specific task
gradle compileJava
gradle compileTestJava

# View all tasks
gradle tasks

3.2 Build Output

Artifact Location: build/libs/lens-1.0.0-RELEASE.jar

Directory Structure:

build/
├── classes/ # Compiled .class files
├── resources/ # Processed resources
├── libs/ # Built JAR file
├── test-results/ # Test results (XML)
├── reports/ # Test reports (HTML)
└── jacoco/ # Code coverage reports

4. Running Locally

4.1 Run with Gradle

# Run with default profile (dev)
gradle bootRun

# Run with specific profile
gradle bootRun --args='--spring.profiles.active=dev'

# Run with custom port
gradle bootRun --args='--server.port=9090'

# Run with debug mode
gradle bootRun --debug-jvm

4.2 Run with Java

# Build first
gradle build -x test

# Run JAR
java -jar build/libs/lens-1.0.0-RELEASE.jar

# Run with profile
java -jar build/libs/lens-1.0.0-RELEASE.jar --spring.profiles.active=dev

# Run with JVM options
java -Xms512m -Xmx2g -jar build/libs/lens-1.0.0-RELEASE.jar

4.3 Verify Application Started

# Check health endpoint
curl http://localhost:8080/lens/actuator/health

# Expected response:
# {"status":"UP"}

# Check info endpoint
curl http://localhost:8080/lens/actuator/info

5. Testing

5.1 Test Structure

src/test/java/com/ttn/ck/lens/
├── controller/ # Controller tests (MockMvc)
├── service/ # Service tests (Mockito)
├── dao/ # DAO tests (Testcontainers)
├── integration/ # Integration tests
└── util/ # Test utilities

5.2 Unit Tests

Run all unit tests:

gradle test

Run specific test class:

gradle test --tests AwsVsActualCostServiceTest

Run specific test method:

gradle test --tests AwsVsActualCostServiceTest.testGetCostSummary

Example Unit Test:

// AwsVsActualCostServiceTest.java
@ExtendWith(MockitoExtension.class)
class AwsVsActualCostServiceTest {

@Mock
private AwsVsActualCostDao dao;

@InjectMocks
private AwsVsActualCostServiceImpl service;

@Test
void testGetCostSummary() {
// Arrange
GenericRequestDTO request = new GenericRequestDTO();
request.setCustomerId("CUST-123");
request.setAccountId("123456789012");
request.setStartDate(LocalDate.of(2024, 1, 1));
request.setEndDate(LocalDate.of(2024, 1, 31));

`List<CostSummaryDTO>` mockData = Arrays.asList(
createCostSummary("EC2", 1000.0, 800.0)
);

when(dao.queryCostSummary(request)).thenReturn(mockData);

// Act
`List<CostSummaryDTO>` result = service.getCostSummary(request);

// Assert
assertNotNull(result);
assertEquals(1, result.size());
assertEquals("EC2", result.get(0).getService());
assertEquals(200.0, result.get(0).getVariance());

verify(dao, times(1)).queryCostSummary(request);
}

private CostSummaryDTO createCostSummary(String service, double awsCost, double actualCost) {
CostSummaryDTO dto = new CostSummaryDTO();
dto.setService(service);
dto.setAwsCost(awsCost);
dto.setActualCost(actualCost);
return dto;
}
}

5.3 Integration Tests

Run integration tests:

gradle integrationTest

Example Integration Test (with Testcontainers):

// AwsVsActualCostIntegrationTest.java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class AwsVsActualCostIntegrationTest {

@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:5.0");

@Container
static MySQLContainer<?> mySQLContainer = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("lens_test");

@Autowired
private TestRestTemplate restTemplate;

@Test
void testGetCostSummaryEndpoint() {
// Arrange
String url = "/admin-pages/cost/summary?customerId=CUST-123&accountId=123456789012&startDate=2024-01-01&endDate=2024-01-31";

HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + generateTestJwt());

HttpEntity<String> entity = new HttpEntity<>(headers);

// Act
`ResponseEntity<ResponseDto>` response = restTemplate.exchange(
url, HttpMethod.GET, entity, ResponseDto.class);

// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("SUCCESS", response.getBody().getStatus());
}

private String generateTestJwt() {
// Generate test JWT token
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
}
}

5.4 Test Coverage

Generate coverage report:

gradle test jacocoTestReport

# View report
open build/reports/jacoco/test/html/index.html

Coverage Targets:

  • Overall: 80%+
  • Controllers: 70%+
  • Services: 90%+
  • DAOs: 85%+

6. Code Style and Conventions

6.1 Java Code Style

Follow Google Java Style Guide with these specifics:

Naming Conventions:

// Classes: PascalCase
public class AwsVsActualCostService { }

// Methods: camelCase
public `List<CostSummaryDTO>` getCostSummary() { }

// Constants: UPPER_SNAKE_CASE
private static final String DEFAULT_CURRENCY = "USD";

// Variables: camelCase
private String customerId;

// DTOs: PascalCase with DTO suffix
public class CostSummaryDTO { }

// Interfaces: PascalCase (no 'I' prefix)
public interface AwsVsActualCostDao { }

// Implementations: PascalCase with Impl suffix
public class AwsVsActualCostDaoImpl implements AwsVsActualCostDao { }

Code Formatting:

// Indentation: 4 spaces (no tabs)
public class Example {
public void method() {
if (condition) {
doSomething();
}
}
}

// Line length: Max 120 characters

// Braces: K&R style
if (condition) {
doSomething();
} else {
doSomethingElse();
}

// Import order:
// 1. Java standard libraries
// 2. Third-party libraries
// 3. Project imports
import java.util.List;
import java.time.LocalDate;

import org.springframework.stereotype.Service;

import com.ttn.ck.lens.dto.CostSummaryDTO;

6.2 Annotations

Ordering:

@RestController
@RequestMapping("/admin-pages")
@Validated
@AllArgsConstructor
@Secured(key = "LENS_CONTROLLER")
public class ExampleController {
// Class body
}

Common Annotations:

  • @Service - Service layer
  • @Repository - DAO layer
  • @RestController - Controller layer
  • @Autowired / @AllArgsConstructor - Dependency injection
  • @Transactional - Transaction management
  • @Cacheable - Caching
  • @Async - Asynchronous execution
  • @Retryable - Retry logic

6.3 Logging

// Use Slf4j
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class ExampleService {

public void method() {
// Use appropriate log levels
log.debug("Debug information: {}", detail);
log.info("Informational message: {}", info);
log.warn("Warning message: {}", warning);
log.error("Error occurred: {}", error, exception);

// Use parameterized logging (not string concatenation)
log.info("Processing request for customer: {}", customerId); // Good
log.info("Processing request for customer: " + customerId); // Bad
}
}

7. IDE Setup

7.1 IntelliJ IDEA

Import Project:

  1. File → Open → Select lens directory
  2. Select "Gradle" as project type
  3. Wait for Gradle sync to complete

Configure JDK:

  1. File → Project Structure → Project
  2. Set SDK to Java 17
  3. Set language level to 17

Code Style:

  1. File → Settings → Editor → Code Style → Java
  2. Import google-java-format.xml
  3. Enable "Reformat code on save"

Plugins (Recommended):

  • Lombok Plugin
  • Spring Boot Assistant
  • Docker
  • Database Navigator

Run Configuration:

  1. Run → Edit Configurations
  2. Add New Configuration → Spring Boot
  3. Main class: com.ttn.ck.lens.LensApplication
  4. Active profiles: dev
  5. Environment variables: Load from .env file

7.2 VS Code

Extensions:

  • Extension Pack for Java
  • Spring Boot Extension Pack
  • Lombok Annotations Support
  • Docker

settings.json:

{
"java.configuration.updateBuildConfiguration": "automatic",
"java.jdt.ls.vmargs": "-Xmx2G",
"spring-boot.ls.java.heap.max": "2G"
}

8. Debugging

8.1 Debug Mode

IntelliJ IDEA:

  1. Set breakpoints in code
  2. Run → Debug 'LensApplication'
  3. Use debugger controls to step through code

Command Line:

# Run with debug port 5005
gradle bootRun --debug-jvm

# Connect remote debugger on port 5005

8.2 Logging

Enable debug logging:

# application-dev.yml
logging:
level:
com.ttn.ck.lens: DEBUG
org.springframework.web: DEBUG
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE

8.3 Actuator Endpoints

# View all beans
curl http://localhost:8080/lens/actuator/beans

# View configuration properties
curl http://localhost:8080/lens/actuator/configprops

# View environment variables
curl http://localhost:8080/lens/actuator/env

# View metrics
curl http://localhost:8080/lens/actuator/metrics

# View specific metric
curl http://localhost:8080/lens/actuator/metrics/jvm.memory.used

9. Common Development Tasks

9.1 Add New Endpoint

Step 1: Create DTO

// CostBreakdownDTO.java
@Data
public class CostBreakdownDTO {
private String category;
private Double cost;
private Double percentage;
}

Step 2: Create Service Method

// AwsVsActualCostService.java
public interface AwsVsActualCostService {
`List<CostBreakdownDTO>` getCostBreakdown(GenericRequestDTO request);
}

// AwsVsActualCostServiceImpl.java
@Override
public `List<CostBreakdownDTO>` getCostBreakdown(GenericRequestDTO request) {
return dao.queryCostBreakdown(request);
}

Step 3: Create DAO Method

// AwsVsActualCostDao.java
public interface AwsVsActualCostDao {
`List<CostBreakdownDTO>` queryCostBreakdown(GenericRequestDTO request);
}

// AwsVsActualCostDaoImpl.java
@Override
public `List<CostBreakdownDTO>` queryCostBreakdown(GenericRequestDTO request) {
String sql = queryLoader.getQuery("query.cost.breakdown");
return jdbcTemplate.query(sql, costBreakdownRowMapper,
request.getAccountId(), request.getStartDate(), request.getEndDate());
}

Step 4: Add Controller Endpoint

// AwsVsActualCostController.java
@GetMapping("/cost/breakdown")
public ResponseDto<`List<CostBreakdownDTO>`> getCostBreakdown(
@Valid GenericRequestDTO request) {
return new SuccessResponseDto<>(service.getCostBreakdown(request));
}

Step 5: Write Tests

// AwsVsActualCostServiceTest.java
@Test
void testGetCostBreakdown() {
// Test implementation
}

9.2 Add New SQL Query

Step 1: Create SQL file

-- src/main/resources/queries/cost/query.cost.breakdown.sql
SELECT
CASE
WHEN SERVICE LIKE '%EC2%' THEN 'Compute'
WHEN SERVICE LIKE '%RDS%' THEN 'Database'
WHEN SERVICE LIKE '%S3%' THEN 'Storage'
ELSE 'Other'
END AS category,
SUM(COST) AS cost,
(SUM(COST) / (SELECT SUM(COST) FROM COST_DAILY WHERE ACCOUNT_ID = ?) * 100) AS percentage
FROM COST_DAILY
WHERE ACCOUNT_ID = ?
AND DATE BETWEEN ? AND ?
GROUP BY category
ORDER BY cost DESC;

Step 2: Load query in DAO

String sql = queryLoader.getQuery("query.cost.breakdown");

9.3 Add Cache Configuration

@Cacheable(value = "costBreakdown", key = "#request.cacheKey()", ttl = 1800)
public `List<CostBreakdownDTO>` getCostBreakdown(GenericRequestDTO request) {
return dao.queryCostBreakdown(request);
}

10. Contributing Guidelines

10.1 Git Workflow

Branch Naming:

feature/JIRA-123-add-cost-breakdown-endpoint
bugfix/JIRA-456-fix-cache-invalidation
hotfix/JIRA-789-critical-security-fix

Commit Messages:

feat: Add cost breakdown endpoint
fix: Fix cache invalidation issue
docs: Update API documentation
test: Add integration tests for cost alerts
refactor: Refactor DAO layer to use template pattern

Pull Request Process:

  1. Create feature branch from develop
  2. Implement changes with tests
  3. Ensure all tests pass: gradle test
  4. Ensure code coverage >80%: gradle jacocoTestReport
  5. Push to remote and create PR
  6. Request code review from 2+ team members
  7. Address review comments
  8. Merge after approval (squash merge preferred)

10.2 Code Review Checklist

Functionality:

  • Code works as expected
  • Edge cases handled
  • Error handling implemented
  • Logging added

Tests:

  • Unit tests written
  • Integration tests written (if applicable)
  • Test coverage >80%
  • All tests pass

Code Quality:

  • Follows code style guidelines
  • No code duplication
  • Appropriate design patterns used
  • Performance considered

Documentation:

  • Javadoc added for public methods
  • README updated (if applicable)
  • API documentation updated (if new endpoint)

11. Troubleshooting

11.1 Build Issues

Issue: Gradle build fails

# Clean Gradle cache
gradle clean
rm -rf ~/.gradle/caches/

# Re-download dependencies
gradle build --refresh-dependencies

Issue: Out of memory

# Increase Gradle heap size
export GRADLE_OPTS="-Xmx4g"
gradle build

11.2 Runtime Issues

Issue: Application won't start

# Check if port 8080 is already in use
lsof -i :8080

# Kill process using port
kill -9 <PID>

Issue: Database connection failed

# Test database connectivity
telnet localhost 3306 # MySQL
telnet localhost 27017 # MongoDB

# Check Docker containers
docker-compose ps
docker-compose logs mysql

12. Resources

12.1 Documentation

12.2 Internal Resources


Summary

Getting Started: Install Java 17, Gradle, Docker → Clone repo → Start dependencies → Run application

Testing: Unit tests (Mockito) + Integration tests (Testcontainers) + Coverage (JaCoCo)

Code Style: Google Java Style + Lombok + Slf4j logging

Development Workflow: Feature branch → Implement → Test → PR → Review → Merge

Key Commands:

  • gradle build - Build project
  • gradle test - Run tests
  • gradle bootRun - Run application
  • docker-compose up -d - Start dependencies

Document Version: 1.0 Last Updated: October 25, 2025