This document outlines the coding standards and best practices for this project. These standards are based on 2026 industry best practices and ensure maximum code quality, maintainability, and team productivity.
Red-Green-Refactor Cycle:
- Red: Write a failing test first
- Green: Write minimal code to make it pass
- Refactor: Improve code while keeping tests green
Rules:
- ✅ Write tests before implementation
- ✅ Tests must fail for the right reason
- ✅ Write minimal code to pass tests
- ✅ Refactor only when tests are green
- ✅ Maintain test coverage (target: 60% for MVP, 80%+ for production)
Test Structure:
@Test
@DisplayName("Should create a valid entity with all required fields")
void shouldCreateValidEntity() {
// Given
String id = "entity-123";
BigDecimal value = new BigDecimal("150.00");
// When
Entity entity = Entity.builder()
.id(id)
.value(value)
.build();
// Then
assertNotNull(entity.getId());
assertEquals(id, entity.getId());
}Principles:
- Extract common logic into reusable methods/classes
- Use inheritance and composition appropriately
- Create utility classes for shared functionality
- Avoid copy-paste code
Examples:
- ✅ Shared test base classes (
AbstractComponentTest) - ✅ Mapper classes for domain-entity conversion
- ✅ Common configuration classes
- ❌ Duplicate validation logic across services
Guidelines:
- Prefer simple solutions over complex ones
- Avoid premature optimization
- Use language features appropriately (Java 21)
- Write code that is easy to understand
Examples:
- ✅ Use
recordfor immutable data structures - ✅ Use
varfor local variables when type is obvious - ✅ Prefer explicit over implicit
- ❌ Over-engineered abstractions
- Each class should have one reason to change
- Services should have focused responsibilities
- Domain models should represent business concepts only
Example:
// ✅ Good: Single responsibility
@Service
public class DataPollingService {
// Only handles polling logic
}
// ❌ Bad: Multiple responsibilities
@Service
public class DataService {
// Polling, filtering, queueing, persistence...
}- Open for extension, closed for modification
- Use interfaces and abstractions
- Prefer composition over inheritance
Example:
// ✅ Good: Interface allows different implementations
public interface ApiClient {
Flux<Data> fetchData();
}
// ✅ Good: Easy to add new implementations
@Component
public class ExternalApiClientImpl implements ApiClient { }
@Component
public class InternalApiClientImpl implements ApiClient { }- Subtypes must be substitutable for their base types
- Maintain behavioral contracts
- Don't violate interface expectations
- Clients shouldn't depend on interfaces they don't use
- Create focused, specific interfaces
- Avoid "fat" interfaces
Example:
// ✅ Good: Focused interface
public interface GitOperations {
void cloneRepository(String url, String path);
void pull(Repository repository);
}
// ❌ Bad: Too many responsibilities
public interface GitOperations {
void cloneRepository(...);
void pull(...);
void createBranch(...);
void merge(...);
void rebase(...);
// ... 20 more methods
}- Depend on abstractions, not concretions
- High-level modules shouldn't depend on low-level modules
- Both should depend on abstractions
Example:
// ✅ Good: Depends on interface
@Service
public class RepositoryService {
private final GitOperations gitOperations; // Interface
public RepositoryService(GitOperations gitOperations) {
this.gitOperations = gitOperations;
}
}
// ❌ Bad: Depends on concrete class
@Service
public class RepositoryService {
private final JGitOperations gitOperations; // Concrete class
}com.yourproject/
├── domain1/
│ ├── domain/ # Domain models
│ ├── entity/ # JPA entities
│ ├── repository/ # Data access
│ ├── service/ # Business logic
│ ├── mapper/ # Domain-entity mapping
│ └── config/ # Configuration
├── domain2/
│ └── [same structure]
└── shared/
└── [shared components]
- Domain Models: Pure business logic, no JPA annotations
- Entities: JPA persistence layer, separate from domain
- Mappers: Convert between domain and entity layers
- Services: Orchestrate domain operations
- Services:
*Service(e.g.,DataPollingService) - Repositories:
*Repository(e.g.,DataRepository) - Entities:
*Entity(e.g.,DataEntity) - DTOs:
*DTOor descriptive names (e.g.,ApiResponse) - Mappers:
*Mapper(e.g.,DataMapper) - Config:
*Config(e.g.,RedisConfig)
- Actions: Verb-based (e.g.,
fetchData(),processEvent()) - Queries: Boolean/descriptive (e.g.,
isEligible(),meetsCriteria()) - Tests:
should*orwhen*(e.g.,shouldCreateValidEntity())
- Descriptive:
dataAmountnotamt - Boolean:
isEligible,hasError,canProcess - Collections: Plural (e.g.,
items,entities)
- ✅ Records: For immutable data structures
- ✅ Pattern Matching: For type checking and extraction
- ✅ Sealed Classes: For restricted inheritance
- ✅ Text Blocks: For multi-line strings
- ✅ Virtual Threads: For high-concurrency I/O
// ✅ Good: Use var when type is obvious
var entity = Entity.builder()
.id("entity-123")
.build();
// ✅ Good: Pattern matching
if (result instanceof FilterResult fr && fr.shouldProcess()) {
processEntity(fr);
}
// ✅ Good: Text blocks for prompts
String prompt = """
Analyze this data and determine if it should be processed.
Details:
- ID: {id}
- Value: {value}
""";- Unit Tests:
src/test/java/unit/ - Component Tests:
src/test/java/component/ - Test Naming:
*Test.javafor unit,*ComponentTest.javafor component
@ExtendWith(MockitoExtension.class)
@DisplayName("BountyPollingService Tests")
class BountyPollingServiceTest {
@Mock
private AlgoraApiClient apiClient;
@InjectMocks
private BountyPollingService service;
@Test
@DisplayName("Should poll and save new bounties")
void shouldPollAndSaveNewBounties() {
// Given
Bounty bounty = createTestBounty();
when(apiClient.fetchBounties()).thenReturn(Flux.just(bounty));
// When
Flux<Bounty> result = service.pollAlgora();
// Then
StepVerifier.create(result)
.expectNext(bounty)
.verifyComplete();
}
}- Minimum: 60% line coverage (MVP target)
- Target: 80%+ line coverage (production goal)
- Critical Paths: 100% coverage
- Domain Models: 100% coverage
- Exclusions: Config classes, entities, DTOs, main class
- ✅ Fail fast with clear error messages
- ✅ Use appropriate exception types
- ✅ Log errors with context
- ✅ Handle errors at appropriate levels
// ✅ Good: Specific exception
if (repository == null) {
throw new IllegalArgumentException("Repository cannot be null");
}
// ✅ Good: Graceful degradation
try {
return apiClient.fetchBounties();
} catch (Exception e) {
log.error("Failed to fetch bounties", e);
return Flux.empty(); // Fallback
}- When: Explain "why", not "what"
- JavaDoc: For public APIs
- Inline: For complex business logic
/**
* Polls external API for new data and processes them.
*
* @param minimumValue Minimum value to consider
* @return Flux of discovered data items
* @throws ApiException if API call fails
*/
public Flux<Data> pollExternalApi(BigDecimal minimumValue) {
// Implementation
}- ✅ Use
Fluxfor multiple items - ✅ Use
Monofor single items - ✅ Chain operations with
flatMap,filter,map - ✅ Handle errors with
onErrorResume,doOnError
return apiClient.fetchData()
.filter(item -> !existsInDatabase(item))
.filter(item -> item.meetsMinimumValue(minimum))
.flatMap(this::saveAndEnqueue)
.doOnError(error -> log.error("Polling failed", error));- ✅ Validate all external inputs
- ✅ Use parameterized queries (JPA handles this)
- ✅ Verify webhook signatures
- ✅ Sanitize user-provided data
- ✅ Use secure defaults
- ✅ Use virtual threads for I/O-bound operations
- ✅ Leverage reactive streams for backpressure
- ✅ Cache expensive operations (Redis)
- ✅ Use connection pooling
- ✅ Profile before optimizing
- Tests written and passing
- Code follows SOLID principles
- No code duplication (DRY)
- Simple and readable (KISS)
- Proper error handling
- Documentation updated
- No security vulnerabilities
- Performance considerations addressed
- Testing Standards - Test structure, patterns, and best practices
- Code Quality Standards - Code review checklist and quality guidelines
- Common Gotchas and Problems - Avoid common pitfalls and mistakes