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:
- File → Open → Select
lensdirectory - Select "Gradle" as project type
- Wait for Gradle sync to complete
Configure JDK:
- File → Project Structure → Project
- Set SDK to Java 17
- Set language level to 17
Code Style:
- File → Settings → Editor → Code Style → Java
- Import
google-java-format.xml - Enable "Reformat code on save"
Plugins (Recommended):
- Lombok Plugin
- Spring Boot Assistant
- Docker
- Database Navigator
Run Configuration:
- Run → Edit Configurations
- Add New Configuration → Spring Boot
- Main class:
com.ttn.ck.lens.LensApplication - Active profiles:
dev - Environment variables: Load from
.envfile
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:
- Set breakpoints in code
- Run → Debug 'LensApplication'
- 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:
- Create feature branch from
develop - Implement changes with tests
- Ensure all tests pass:
gradle test - Ensure code coverage >80%:
gradle jacocoTestReport - Push to remote and create PR
- Request code review from 2+ team members
- Address review comments
- 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
- Confluence: https://wiki.cloudkeeper.com/lens
- JIRA: https://jira.cloudkeeper.com/projects/LENS
- Slack: #lens-dev channel
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 projectgradle test- Run testsgradle bootRun- Run applicationdocker-compose up -d- Start dependencies
Document Version: 1.0 Last Updated: October 25, 2025