feat: Add complete HSP implementation with integration tests passing
Initial implementation of HTTP Sender Plugin following TDD methodology with hexagonal architecture. All 313 tests passing (0 failures). This commit adds: - Complete domain model and port interfaces - All adapter implementations (HTTP, gRPC, file logging, config) - Application services (data collection, transmission, backpressure) - Comprehensive test suite with 18 integration tests Test fixes applied during implementation: - Fix base64 encoding validation in DataCollectionServiceIntegrationTest - Fix exception type handling in IConfigurationPortTest - Fix CompletionException unwrapping in IHttpPollingPortTest - Fix sequential batching in DataTransmissionServiceIntegrationTest - Add test adapter failure simulation for reconnection tests - Use adapter counters for gRPC verification Files added: - pom.xml with all dependencies (JUnit 5, Mockito, WireMock, gRPC, Jackson) - src/main/java: Domain model, ports, adapters, application services - src/test/java: Unit tests, integration tests, test utilities
This commit is contained in:
@@ -0,0 +1,573 @@
|
||||
# Code Review Guidelines
|
||||
**Version**: 1.0
|
||||
**Project**: HTTP Sender Plugin (HSP)
|
||||
**Last Updated**: 2025-11-20
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines code review standards and processes for the HSP project, ensuring code quality, maintainability, security, and compliance with hexagonal architecture and TDD methodology.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Code Review Objectives
|
||||
|
||||
1. **Quality Assurance**: Ensure code meets quality standards and best practices
|
||||
2. **Knowledge Sharing**: Spread knowledge across the team
|
||||
3. **Bug Prevention**: Catch defects before they reach production
|
||||
4. **Architecture Compliance**: Verify hexagonal architecture boundaries
|
||||
5. **TDD Verification**: Ensure Test-Driven Development methodology followed
|
||||
6. **Security Review**: Identify potential vulnerabilities
|
||||
7. **Performance Review**: Spot optimization opportunities
|
||||
8. **Maintainability**: Ensure code is readable and maintainable
|
||||
|
||||
---
|
||||
|
||||
## 📋 Review Process Overview
|
||||
|
||||
### 1. Pull Request Creation
|
||||
|
||||
**Developer Responsibilities**:
|
||||
- [ ] Create feature branch from develop
|
||||
- [ ] Implement using TDD (RED-GREEN-REFACTOR)
|
||||
- [ ] Ensure all tests pass locally
|
||||
- [ ] Run coverage analysis (≥95%/90%)
|
||||
- [ ] Self-review changes before creating PR
|
||||
- [ ] Create PR in Gitea with detailed description
|
||||
|
||||
**PR Title Format**:
|
||||
```
|
||||
[TYPE] Brief description
|
||||
|
||||
Examples:
|
||||
feat: implement BufferManager with FIFO overflow
|
||||
fix: correct retry logic in HttpPollingAdapter
|
||||
refactor: improve DataCollectionService naming
|
||||
test: add integration tests for gRPC transmission
|
||||
docs: update architecture diagrams
|
||||
```
|
||||
|
||||
**PR Description Template**:
|
||||
```markdown
|
||||
## Summary
|
||||
[Brief description of changes]
|
||||
|
||||
## Related Requirements
|
||||
- Req-FR-XX: [requirement description]
|
||||
- Req-NFR-XX: [requirement description]
|
||||
|
||||
## TDD Compliance
|
||||
- [ ] Tests written before implementation
|
||||
- [ ] Git history shows RED-GREEN-REFACTOR cycles
|
||||
- [ ] All tests passing
|
||||
- [ ] Coverage: XX% line, XX% branch
|
||||
|
||||
## Testing
|
||||
- [ ] Unit tests added/updated
|
||||
- [ ] Integration tests added/updated (if applicable)
|
||||
- [ ] Manual testing performed
|
||||
|
||||
## Architecture Impact
|
||||
- [ ] Follows hexagonal architecture
|
||||
- [ ] Port interfaces not violated
|
||||
- [ ] Domain models remain pure
|
||||
- [ ] Adapters properly isolated
|
||||
|
||||
## Checklist
|
||||
- [ ] Self-reviewed code
|
||||
- [ ] Documentation updated
|
||||
- [ ] No commented-out code
|
||||
- [ ] No debug statements
|
||||
- [ ] Thread safety verified (if applicable)
|
||||
- [ ] CI pipeline passing
|
||||
```
|
||||
|
||||
### 2. Reviewer Assignment
|
||||
|
||||
**Assignment Rules**:
|
||||
- At least **2 reviewers required** for all PRs
|
||||
- **1 senior developer** must review architectural changes
|
||||
- **QA engineer** must review test changes
|
||||
- **Security specialist** for security-sensitive code (authentication, data handling)
|
||||
|
||||
**Review SLA**:
|
||||
- **Initial feedback**: Within 4 hours
|
||||
- **Complete review**: Within 24 hours
|
||||
- **Critical fixes**: Within 2 hours
|
||||
|
||||
### 3. Review Checklist
|
||||
|
||||
Reviewers must verify all items in this checklist before approval.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Code Review Checklist
|
||||
|
||||
### 1. TDD Compliance ⚠️ MANDATORY
|
||||
|
||||
**Git History Verification**:
|
||||
- [ ] **Tests committed BEFORE implementation** (check timestamps)
|
||||
- [ ] Clear RED-GREEN-REFACTOR cycle in commit history
|
||||
- [ ] Commit messages follow TDD pattern (`test:`, `feat:`, `refactor:`)
|
||||
- [ ] Test-to-code commit ratio approximately 1:1
|
||||
- [ ] No large implementation commits without prior test commits
|
||||
|
||||
**Test Quality**:
|
||||
- [ ] Tests follow AAA pattern (Arrange-Act-Assert)
|
||||
- [ ] Test names describe behavior: `shouldDoX_whenY()`
|
||||
- [ ] Tests are independent (no shared state)
|
||||
- [ ] Tests are repeatable (deterministic results)
|
||||
- [ ] Edge cases covered (boundary conditions, empty inputs, nulls)
|
||||
- [ ] Error scenarios tested (exceptions, timeouts, failures)
|
||||
- [ ] Both happy path and sad path tested
|
||||
|
||||
**Coverage Verification**:
|
||||
- [ ] JaCoCo report shows ≥95% line coverage
|
||||
- [ ] JaCoCo report shows ≥90% branch coverage
|
||||
- [ ] New code is covered by new tests (not just existing tests)
|
||||
- [ ] No critical paths left untested
|
||||
- [ ] CI pipeline coverage check passes
|
||||
|
||||
### 2. Hexagonal Architecture Compliance ⚠️ MANDATORY
|
||||
|
||||
**Port Boundary Enforcement**:
|
||||
- [ ] **Ports not bypassed**: Application core only depends on port interfaces
|
||||
- [ ] **No adapter imports in core**: Domain services don't import adapters
|
||||
- [ ] **Port interfaces defined**: All external interactions through ports
|
||||
- [ ] **Dependency direction correct**: Core → Ports ← Adapters (not Core → Adapters)
|
||||
|
||||
**Domain Model Purity**:
|
||||
- [ ] **No infrastructure in domain**: No HTTP, gRPC, file I/O in domain models
|
||||
- [ ] **Immutable value objects**: All domain models are immutable (final fields, no setters)
|
||||
- [ ] **Business logic in core**: Not in adapters
|
||||
- [ ] **Domain-specific language used**: Clear, business-focused names
|
||||
|
||||
**Adapter Isolation**:
|
||||
- [ ] **Adapters implement ports**: Each adapter implements one or more port interfaces
|
||||
- [ ] **Infrastructure isolated**: HTTP, gRPC, file I/O only in adapters
|
||||
- [ ] **Adapters don't talk directly**: Communication through application core
|
||||
- [ ] **Configuration injected**: No hardcoded values in adapters
|
||||
|
||||
**Package Structure Verification**:
|
||||
```
|
||||
✓ CORRECT:
|
||||
com.siemens.coreshield.hsp.
|
||||
├── domain/ (models, pure Java)
|
||||
├── application/ (services, depends on ports)
|
||||
├── ports/
|
||||
│ ├── inbound/ (primary ports)
|
||||
│ └── outbound/ (secondary ports)
|
||||
└── adapter/
|
||||
├── inbound/ (implements primary ports)
|
||||
└── outbound/ (implements secondary ports)
|
||||
|
||||
✗ WRONG:
|
||||
application.DataCollectionService imports adapter.HttpPollingAdapter
|
||||
domain.DiagnosticData imports io.grpc.stub
|
||||
```
|
||||
|
||||
### 3. Thread Safety (Concurrency Requirements)
|
||||
|
||||
**Concurrency Review** (CRITICAL for HSP):
|
||||
- [ ] **Thread-safe where required**: BufferManager, CollectionStatistics
|
||||
- [ ] **Immutability used**: Value objects are thread-safe by design
|
||||
- [ ] **Proper synchronization**: `synchronized`, `Lock`, or concurrent collections
|
||||
- [ ] **Atomic operations**: Use `AtomicLong`, `AtomicInteger` for counters
|
||||
- [ ] **No race conditions**: Shared state properly protected
|
||||
- [ ] **Virtual threads used correctly**: For I/O-bound tasks (HTTP polling)
|
||||
- [ ] **Stress tests exist**: Concurrent tests with 1000+ threads (if applicable)
|
||||
|
||||
**Common Thread Safety Issues**:
|
||||
```java
|
||||
✗ WRONG:
|
||||
public class CollectionStatistics {
|
||||
private int totalPolls = 0; // Not thread-safe!
|
||||
|
||||
public void incrementPolls() {
|
||||
totalPolls++; // Race condition
|
||||
}
|
||||
}
|
||||
|
||||
✓ CORRECT:
|
||||
public class CollectionStatistics {
|
||||
private final AtomicInteger totalPolls = new AtomicInteger(0);
|
||||
|
||||
public void incrementPolls() {
|
||||
totalPolls.incrementAndGet();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Code Quality
|
||||
|
||||
**Readability**:
|
||||
- [ ] Clear, descriptive variable names (not `x`, `tmp`, `data123`)
|
||||
- [ ] Methods are small (<30 lines preferred)
|
||||
- [ ] Classes are focused (Single Responsibility Principle)
|
||||
- [ ] No commented-out code (use Git history instead)
|
||||
- [ ] No debug statements (`System.out.println`, excessive logging)
|
||||
- [ ] Complex logic has explanatory comments
|
||||
|
||||
**SOLID Principles**:
|
||||
- [ ] **Single Responsibility**: Each class has one reason to change
|
||||
- [ ] **Open/Closed**: Open for extension, closed for modification
|
||||
- [ ] **Liskov Substitution**: Subtypes are substitutable for base types
|
||||
- [ ] **Interface Segregation**: Interfaces are client-specific, not fat
|
||||
- [ ] **Dependency Inversion**: Depend on abstractions (ports), not concretions
|
||||
|
||||
**DRY (Don't Repeat Yourself)**:
|
||||
- [ ] No duplicated code (extract to methods/utilities)
|
||||
- [ ] No copy-paste programming
|
||||
- [ ] Common logic extracted to reusable components
|
||||
|
||||
**Error Handling**:
|
||||
- [ ] Exceptions used appropriately (not for control flow)
|
||||
- [ ] Custom exceptions for domain errors
|
||||
- [ ] Resources cleaned up (try-with-resources, finally blocks)
|
||||
- [ ] Error messages are descriptive and actionable
|
||||
- [ ] Logging at appropriate levels (ERROR, WARN, INFO, DEBUG)
|
||||
|
||||
### 5. Performance
|
||||
|
||||
**Algorithm Efficiency**:
|
||||
- [ ] Appropriate data structures used (O(1) for lookups if possible)
|
||||
- [ ] No unnecessary iterations (nested loops reviewed)
|
||||
- [ ] No premature optimization (profile first)
|
||||
- [ ] Database queries optimized (no N+1 queries, though HSP has no DB)
|
||||
|
||||
**Resource Management**:
|
||||
- [ ] No memory leaks (collections not unbounded)
|
||||
- [ ] Connections properly closed (HTTP clients, gRPC channels)
|
||||
- [ ] Streams closed (try-with-resources)
|
||||
- [ ] Thread pools bounded (use virtual threads or fixed pools)
|
||||
|
||||
**Caching** (if applicable):
|
||||
- [ ] Appropriate cache eviction strategy
|
||||
- [ ] Thread-safe cache access
|
||||
- [ ] Cache invalidation logic correct
|
||||
|
||||
### 6. Security
|
||||
|
||||
**Input Validation**:
|
||||
- [ ] **All external inputs validated** (HTTP responses, configuration)
|
||||
- [ ] Size limits enforced (Req-FR-21: 1MB limit)
|
||||
- [ ] URL validation (protocol, host, port)
|
||||
- [ ] JSON validation (schema compliance)
|
||||
- [ ] No injection vulnerabilities (though HSP has limited attack surface)
|
||||
|
||||
**Data Handling**:
|
||||
- [ ] **Sensitive data not logged** (no raw data in logs)
|
||||
- [ ] Base64 encoding used for binary data (Req-FR-22)
|
||||
- [ ] No credentials in code (use configuration)
|
||||
- [ ] Configuration file permissions documented
|
||||
|
||||
**Error Information Disclosure**:
|
||||
- [ ] Error messages don't expose internals
|
||||
- [ ] Stack traces not sent to external systems
|
||||
- [ ] Logs sanitized (no sensitive data)
|
||||
|
||||
### 7. Documentation
|
||||
|
||||
**Code Documentation**:
|
||||
- [ ] **Javadoc on all public APIs** (classes, methods, interfaces)
|
||||
- [ ] **Requirement traceability in Javadoc**: `@requirement Req-FR-XX`
|
||||
- [ ] Complex algorithms explained (comments or Javadoc)
|
||||
- [ ] Non-obvious behavior documented
|
||||
- [ ] TODOs tracked (with ticket numbers if applicable)
|
||||
|
||||
**Javadoc Example**:
|
||||
```java
|
||||
/**
|
||||
* Polls the specified HTTP endpoint and returns the response data.
|
||||
*
|
||||
* <p>This method implements the HTTP polling logic with retry and backoff
|
||||
* strategy as specified in requirements Req-FR-17 and Req-FR-18.
|
||||
*
|
||||
* @requirement Req-FR-14 HTTP endpoint polling
|
||||
* @requirement Req-FR-17 Retry mechanism (3 attempts, 5s interval)
|
||||
* @requirement Req-FR-18 Linear backoff (5s to 300s)
|
||||
*
|
||||
* @param url the HTTP endpoint URL to poll
|
||||
* @return a CompletableFuture containing the response data
|
||||
* @throws PollingException if all retry attempts fail
|
||||
*/
|
||||
CompletableFuture<byte[]> pollEndpoint(String url);
|
||||
```
|
||||
|
||||
**README/Documentation Updates**:
|
||||
- [ ] README updated if public APIs changed
|
||||
- [ ] Architecture diagrams updated if structure changed
|
||||
- [ ] Configuration examples updated if config changed
|
||||
- [ ] Operations documentation updated if deployment changed
|
||||
|
||||
### 8. Testing
|
||||
|
||||
**Unit Tests**:
|
||||
- [ ] All public methods tested
|
||||
- [ ] Edge cases covered (empty, null, boundary values)
|
||||
- [ ] Exception handling tested
|
||||
- [ ] Mock objects used appropriately (not over-mocked)
|
||||
- [ ] Tests are fast (<100ms per test for unit tests)
|
||||
|
||||
**Integration Tests**:
|
||||
- [ ] Component boundaries tested (adapter ↔ application core)
|
||||
- [ ] External systems mocked (WireMock for HTTP, gRPC test server)
|
||||
- [ ] Retry and backoff logic tested
|
||||
- [ ] Timeout behavior tested
|
||||
- [ ] Failure scenarios tested
|
||||
|
||||
**Test Naming**:
|
||||
```java
|
||||
✓ GOOD:
|
||||
shouldRetryThreeTimes_whenHttpReturns500()
|
||||
shouldDiscardOldest_whenBufferFull()
|
||||
shouldRejectData_whenSizeExceeds1MB()
|
||||
|
||||
✗ BAD:
|
||||
testBuffer()
|
||||
testMethod1()
|
||||
test_success()
|
||||
```
|
||||
|
||||
### 9. Configuration and Deployment
|
||||
|
||||
**Configuration**:
|
||||
- [ ] No hardcoded values (use configuration)
|
||||
- [ ] Environment-specific values externalized
|
||||
- [ ] Configuration validated at startup (Req-FR-11)
|
||||
- [ ] Configuration schema documented
|
||||
|
||||
**Build and Deployment**:
|
||||
- [ ] Maven build succeeds: `mvn clean package`
|
||||
- [ ] All tests pass: `mvn test`
|
||||
- [ ] Coverage check passes: `mvn jacoco:check`
|
||||
- [ ] Fat JAR builds correctly (if applicable)
|
||||
- [ ] No build warnings (deprecations, unchecked casts)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Review Depth by Change Type
|
||||
|
||||
### Minor Changes (< 50 lines)
|
||||
- Quick review (15-30 minutes)
|
||||
- Focus on correctness and tests
|
||||
- One reviewer sufficient
|
||||
|
||||
### Medium Changes (50-200 lines)
|
||||
- Detailed review (30-60 minutes)
|
||||
- Full checklist review
|
||||
- Architecture impact assessment
|
||||
- Two reviewers required
|
||||
|
||||
### Major Changes (> 200 lines)
|
||||
- In-depth review (1-2 hours)
|
||||
- Break into smaller PRs if possible
|
||||
- Architecture review session if needed
|
||||
- Two reviewers + architect approval
|
||||
|
||||
### Critical Components
|
||||
**Always require senior review**:
|
||||
- BufferManager (thread safety critical)
|
||||
- DataTransmissionService (data loss prevention)
|
||||
- GrpcStreamAdapter (protocol correctness)
|
||||
- ConfigurationManager (system stability)
|
||||
- Security-related code
|
||||
|
||||
---
|
||||
|
||||
## 💬 Review Feedback Guidelines
|
||||
|
||||
### How to Provide Feedback
|
||||
|
||||
**Be Constructive**:
|
||||
- Focus on the code, not the person
|
||||
- Explain WHY something is an issue
|
||||
- Provide concrete suggestions or alternatives
|
||||
- Acknowledge good practices
|
||||
|
||||
**Use Clear Categories**:
|
||||
- **🚨 BLOCKER**: Must be fixed before merge (security, correctness)
|
||||
- **⚠️ MAJOR**: Should be fixed before merge (quality, maintainability)
|
||||
- **💡 SUGGESTION**: Consider improving (nice-to-have)
|
||||
- **❓ QUESTION**: Clarification needed
|
||||
- **👍 PRAISE**: Good work, best practice
|
||||
|
||||
**Example Feedback**:
|
||||
```markdown
|
||||
🚨 BLOCKER: Thread Safety Issue
|
||||
Line 45: `totalPolls++` is not thread-safe.
|
||||
Use `AtomicInteger.incrementAndGet()` instead.
|
||||
This could cause incorrect statistics under concurrent access.
|
||||
|
||||
⚠️ MAJOR: Missing Null Check
|
||||
Line 120: `data.getBytes()` could throw NPE if data is null.
|
||||
Add validation: `Objects.requireNonNull(data, "data cannot be null")`
|
||||
|
||||
💡 SUGGESTION: Consider Extracting Method
|
||||
Lines 200-250: This method is 50 lines long and does multiple things.
|
||||
Consider extracting validation logic to `validateConfiguration()`.
|
||||
|
||||
👍 PRAISE: Excellent Test Coverage
|
||||
Great job on the comprehensive edge case testing! The boundary value
|
||||
tests (lines 50-75) are exactly what we need for high-quality code.
|
||||
```
|
||||
|
||||
### How to Receive Feedback
|
||||
|
||||
**Be Professional**:
|
||||
- Don't take feedback personally
|
||||
- Ask for clarification if unclear
|
||||
- Discuss disagreements respectfully
|
||||
- Update code based on feedback
|
||||
- Respond to all comments (even "Done" is helpful)
|
||||
|
||||
**Address All Feedback**:
|
||||
- Fix blockers immediately
|
||||
- Discuss major issues if disagreement
|
||||
- Consider suggestions (but can defer)
|
||||
- Answer questions thoroughly
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Review Priorities (What to Focus On)
|
||||
|
||||
### Priority 1: CRITICAL (Must Review)
|
||||
1. **TDD Compliance**: Tests before code
|
||||
2. **Thread Safety**: Concurrent access correctness
|
||||
3. **Architecture Boundaries**: Hexagonal architecture
|
||||
4. **Security**: Input validation, data handling
|
||||
5. **Correctness**: Logic errors, edge cases
|
||||
|
||||
### Priority 2: IMPORTANT (Should Review)
|
||||
6. **Test Coverage**: 95%/90% thresholds
|
||||
7. **Error Handling**: Exception handling, resource cleanup
|
||||
8. **Performance**: Algorithm efficiency, resource management
|
||||
9. **Documentation**: Javadoc, requirement traceability
|
||||
10. **Code Quality**: SOLID principles, readability
|
||||
|
||||
### Priority 3: NICE-TO-HAVE (Good to Review)
|
||||
11. **Style**: Consistent formatting (automate with Checkstyle)
|
||||
12. **Naming**: Improved variable names
|
||||
13. **Comments**: Additional explanatory comments
|
||||
14. **Refactoring**: Simplification opportunities
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Approval Criteria
|
||||
|
||||
**Code can be merged when**:
|
||||
- [ ] **All blockers resolved** (no outstanding 🚨 issues)
|
||||
- [ ] **At least 2 approvals** (or 1 if minor change)
|
||||
- [ ] **CI pipeline green** (all tests passing, coverage met)
|
||||
- [ ] **TDD compliance verified** (tests before code in Git history)
|
||||
- [ ] **Architecture compliance verified** (hexagonal boundaries respected)
|
||||
- [ ] **Documentation updated** (Javadoc, README if needed)
|
||||
- [ ] **All reviewer comments addressed** (or explicitly deferred with reason)
|
||||
|
||||
**Merge Process**:
|
||||
1. Developer resolves all feedback
|
||||
2. Reviewers re-review and approve
|
||||
3. CI pipeline passes (automated check)
|
||||
4. Developer merges to develop branch (or tech lead merges to main)
|
||||
5. Delete feature branch after merge
|
||||
|
||||
---
|
||||
|
||||
## 📊 Review Metrics
|
||||
|
||||
### Track Per Sprint
|
||||
| Metric | Target | Purpose |
|
||||
|--------|--------|---------|
|
||||
| Average Review Time | <24h | Responsiveness |
|
||||
| Review Thoroughness | 100% checklist | Completeness |
|
||||
| Blockers Found | Track trend | Code quality indicator |
|
||||
| TDD Compliance Rate | 100% | Process adherence |
|
||||
| PRs Requiring Rework | <30% | Quality of first submission |
|
||||
|
||||
### Review Effectiveness Indicators
|
||||
- **Bugs found in review vs. post-merge**: Higher ratio = better reviews
|
||||
- **Time to merge**: Faster = efficient process
|
||||
- **Review comments per PR**: Too high = need better self-review; too low = superficial review
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Review Tools
|
||||
|
||||
### Gitea Code Review Features
|
||||
- **Line-by-line comments**: Comment on specific code lines
|
||||
- **Review status**: Request changes, approve, comment
|
||||
- **PR templates**: Use standardized PR descriptions
|
||||
- **Labels**: Tag PRs (bug, feature, refactor, etc.)
|
||||
- **Assignees**: Assign specific reviewers
|
||||
|
||||
### IDE Integration
|
||||
- **IntelliJ IDEA**: Gitea plugin for in-IDE reviews
|
||||
- **Eclipse**: EGit for Git integration
|
||||
- **VS Code**: GitLens for Git history visualization
|
||||
|
||||
### Automated Checks (CI Pipeline)
|
||||
- **Compile Check**: Code compiles without errors
|
||||
- **Unit Tests**: All tests pass
|
||||
- **Coverage Check**: JaCoCo enforces 95%/90%
|
||||
- **Checkstyle**: Code style compliance (if configured)
|
||||
- **SpotBugs**: Static analysis for bugs (if configured)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Training and Resources
|
||||
|
||||
### New Reviewers
|
||||
- Pair with senior reviewer for first 5 PRs
|
||||
- Review this document thoroughly
|
||||
- Practice with historical PRs
|
||||
- Ask questions in team chat
|
||||
|
||||
### Internal Resources
|
||||
- [TDD Compliance Checklist](TDD_COMPLIANCE_CHECKLIST.md)
|
||||
- [Thread Safety Guidelines](THREAD_SAFETY_GUIDELINES.md)
|
||||
- [Pull Request Template](PULL_REQUEST_TEMPLATE.md)
|
||||
- [Architecture Decisions](../ARCHITECTURE_DECISIONS.md)
|
||||
|
||||
### External Resources
|
||||
- "Code Complete" by Steve McConnell (Chapter on Code Reviews)
|
||||
- "The Art of Readable Code" by Boswell & Foucher
|
||||
- Google Engineering Practices: Code Review Guide
|
||||
|
||||
---
|
||||
|
||||
## 📞 Questions and Support
|
||||
|
||||
**Code Review Questions**:
|
||||
- **Process**: Project Manager
|
||||
- **Technical**: Tech Lead, Senior Developers
|
||||
- **Architecture**: Architect
|
||||
- **Testing/TDD**: QA Lead
|
||||
|
||||
**Escalation**:
|
||||
- If reviewers disagree: Tech Lead decides
|
||||
- If unsure about architecture: Architect reviews
|
||||
- If security concern: Security specialist reviews
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Summary: Review Mindset
|
||||
|
||||
> **"Code review is not about finding fault—it's about building better software together."**
|
||||
|
||||
### The Three Goals of Code Review
|
||||
1. **Improve the code**: Make it better through collaboration
|
||||
2. **Share knowledge**: Learn from each other
|
||||
3. **Maintain quality**: Ensure standards are met consistently
|
||||
|
||||
### The Review Promise
|
||||
- **Be respectful**: Critique code, not people
|
||||
- **Be thorough**: Follow the checklist consistently
|
||||
- **Be timely**: Review within 24 hours
|
||||
- **Be constructive**: Suggest improvements, don't just criticize
|
||||
|
||||
---
|
||||
|
||||
**Document Control**:
|
||||
- Version: 1.0
|
||||
- Created: 2025-11-20
|
||||
- Status: Active
|
||||
- Review Cycle: After each sprint
|
||||
@@ -0,0 +1,385 @@
|
||||
# Pull Request Template
|
||||
**Project**: HTTP Sender Plugin (HSP)
|
||||
|
||||
---
|
||||
|
||||
## 📝 PR Title
|
||||
<!-- Use format: [TYPE] Brief description -->
|
||||
<!-- Examples:
|
||||
feat: implement BufferManager with FIFO overflow
|
||||
fix: correct retry logic in HttpPollingAdapter
|
||||
refactor: improve DataCollectionService naming
|
||||
test: add integration tests for gRPC transmission
|
||||
docs: update architecture diagrams
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## 📋 Summary
|
||||
<!-- Provide a brief description of what this PR does (2-3 sentences) -->
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Related Requirements
|
||||
<!-- List all requirements this PR implements/addresses -->
|
||||
<!-- Use format: - Req-XX: Description -->
|
||||
<!-- Find requirements in: docs/requirements/DataCollector SRS.md -->
|
||||
|
||||
- Req-FR-XX: [Requirement description]
|
||||
- Req-NFR-XX: [Requirement description]
|
||||
- Req-Arch-XX: [Requirement description]
|
||||
|
||||
---
|
||||
|
||||
## ✅ TDD Compliance ⚠️ MANDATORY
|
||||
|
||||
### Git History Verification
|
||||
<!-- Reviewers will verify the following -->
|
||||
|
||||
- [ ] **Tests committed BEFORE implementation** (check Git timestamps)
|
||||
- [ ] **Git history shows RED-GREEN-REFACTOR cycle** (test → feat → refactor commits)
|
||||
- [ ] **Commit messages follow TDD pattern** (`test: ... (RED)`, `feat: ... (GREEN)`, `refactor: ...`)
|
||||
|
||||
### Test Quality
|
||||
- [ ] **Tests follow AAA pattern** (Arrange-Act-Assert)
|
||||
- [ ] **Test names describe behavior**: `shouldDoX_whenY()`
|
||||
- [ ] **Edge cases covered** (boundary conditions, nulls, empty)
|
||||
- [ ] **Error scenarios tested** (exceptions, timeouts, failures)
|
||||
- [ ] **Both happy path and sad path tested**
|
||||
|
||||
### Coverage Verification
|
||||
- [ ] **Line coverage**: ___% (target: ≥95%)
|
||||
- [ ] **Branch coverage**: ___% (target: ≥90%)
|
||||
- [ ] **JaCoCo report generated**: `mvn jacoco:report` (attach link or screenshot)
|
||||
- [ ] **CI coverage check passes**
|
||||
|
||||
### TDD Evidence
|
||||
<!-- Provide Git log excerpt showing RED-GREEN-REFACTOR cycle -->
|
||||
```
|
||||
Example:
|
||||
abc1234 test: add BufferManager offer() test (RED)
|
||||
def5678 feat: implement BufferManager offer() method (GREEN)
|
||||
ghi9012 refactor: add javadoc to BufferManager
|
||||
|
||||
jkl3456 test: add BufferManager overflow test (RED)
|
||||
mno7890 feat: implement FIFO overflow handling (GREEN)
|
||||
pqr1234 refactor: improve naming in overflow logic
|
||||
```
|
||||
|
||||
**Git Log**:
|
||||
```
|
||||
<!-- Paste relevant commit history here -->
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Impact
|
||||
|
||||
### Hexagonal Architecture Compliance
|
||||
- [ ] **Follows hexagonal architecture** (domain → ports → adapters)
|
||||
- [ ] **Port interfaces not violated** (no direct adapter access from core)
|
||||
- [ ] **Domain models remain pure** (no infrastructure dependencies)
|
||||
- [ ] **Adapters properly isolated** (implement port interfaces)
|
||||
- [ ] **Dependency direction correct**: Core → Ports ← Adapters
|
||||
|
||||
### Components Affected
|
||||
<!-- Check all that apply -->
|
||||
- [ ] Domain Models (`com.siemens.coreshield.hsp.domain`)
|
||||
- [ ] Application Services (`com.siemens.coreshield.hsp.application`)
|
||||
- [ ] Port Interfaces (`com.siemens.coreshield.hsp.ports.inbound|outbound`)
|
||||
- [ ] Inbound Adapters (`com.siemens.coreshield.hsp.adapter.inbound`)
|
||||
- [ ] Outbound Adapters (`com.siemens.coreshield.hsp.adapter.outbound`)
|
||||
- [ ] Configuration
|
||||
- [ ] Build/Deployment
|
||||
|
||||
### Architecture Diagram Impact
|
||||
- [ ] **No architecture changes** (implementation only)
|
||||
- [ ] **Minor architecture changes** (new adapter, new port method)
|
||||
- [ ] **Major architecture changes** (new component, structural change)
|
||||
|
||||
<!-- If major changes, update diagrams in docs/diagrams/ -->
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Unit Tests
|
||||
- [ ] **Unit tests added** for all new code
|
||||
- [ ] **Unit tests updated** for changed code
|
||||
- [ ] **All unit tests pass** locally: `mvn test`
|
||||
- [ ] **Mock objects used appropriately** (external dependencies mocked)
|
||||
|
||||
### Integration Tests
|
||||
<!-- Check if applicable -->
|
||||
- [ ] **Integration tests added** (if new component or adapter)
|
||||
- [ ] **Integration tests updated** (if component behavior changed)
|
||||
- [ ] **All integration tests pass** locally: `mvn verify -P integration-tests`
|
||||
- [ ] **External systems mocked** (WireMock for HTTP, gRPC test server)
|
||||
|
||||
### Performance Tests
|
||||
<!-- Check if applicable to performance-sensitive code -->
|
||||
- [ ] **Performance tests added** (if performance-sensitive code)
|
||||
- [ ] **Benchmarks run** (if applicable)
|
||||
- [ ] **No performance regression** (compared to baseline)
|
||||
|
||||
### Manual Testing
|
||||
- [ ] **Manual testing performed** (describe below)
|
||||
|
||||
**Manual Testing Details**:
|
||||
```
|
||||
<!-- Describe what you tested manually -->
|
||||
Example:
|
||||
- Tested BufferManager with 1000 concurrent threads
|
||||
- Verified overflow behavior discards oldest items
|
||||
- Confirmed statistics are accurate under load
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Thread Safety (if applicable)
|
||||
|
||||
<!-- Only fill out if code involves concurrency/shared state -->
|
||||
|
||||
- [ ] **Thread-safe implementation** (if required by requirements)
|
||||
- [ ] **Immutability used** (value objects, final fields)
|
||||
- [ ] **Proper synchronization** (`synchronized`, `Lock`, concurrent collections)
|
||||
- [ ] **Atomic operations used** (`AtomicLong`, `AtomicInteger` for counters)
|
||||
- [ ] **No race conditions** (shared state properly protected)
|
||||
- [ ] **Stress tests exist** (concurrent tests with 100+ threads)
|
||||
|
||||
**Thread Safety Justification**:
|
||||
```
|
||||
<!-- Explain why this code is thread-safe, or why it doesn't need to be -->
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Considerations
|
||||
|
||||
### Input Validation
|
||||
- [ ] **All external inputs validated** (HTTP responses, configuration)
|
||||
- [ ] **Size limits enforced** (Req-FR-21: 1MB limit if applicable)
|
||||
- [ ] **URL validation** (protocol, host, port if applicable)
|
||||
- [ ] **JSON validation** (schema compliance if applicable)
|
||||
|
||||
### Data Handling
|
||||
- [ ] **Sensitive data not logged** (no raw data in logs)
|
||||
- [ ] **Base64 encoding used** (for binary data if applicable)
|
||||
- [ ] **No credentials in code** (use configuration)
|
||||
|
||||
### Security Review Needed?
|
||||
- [ ] **No security-sensitive code** (skip security review)
|
||||
- [ ] **Security review recommended** (involves authentication, data handling, external input)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### Code Documentation
|
||||
- [ ] **Javadoc added** for all public APIs (classes, methods, interfaces)
|
||||
- [ ] **Requirement traceability in Javadoc**: `@requirement Req-FR-XX`
|
||||
- [ ] **Complex logic explained** (comments or Javadoc)
|
||||
- [ ] **TODOs tracked** (with ticket numbers if applicable)
|
||||
|
||||
### External Documentation
|
||||
- [ ] **README updated** (if public APIs changed)
|
||||
- [ ] **Architecture diagrams updated** (if structure changed)
|
||||
- [ ] **Configuration examples updated** (if config changed)
|
||||
- [ ] **Operations documentation updated** (if deployment changed)
|
||||
|
||||
**Documentation Changes**:
|
||||
<!-- List files changed or reference "None" -->
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration Changes
|
||||
|
||||
<!-- Only fill out if configuration changed -->
|
||||
|
||||
- [ ] **No configuration changes**
|
||||
- [ ] **Configuration schema updated** (hsp-config.json)
|
||||
- [ ] **Configuration validation updated** (ConfigurationValidator)
|
||||
- [ ] **Configuration example updated** (docs/examples/)
|
||||
- [ ] **Backward compatible** (or migration path documented)
|
||||
|
||||
**Configuration Impact**:
|
||||
```
|
||||
<!-- Describe configuration changes -->
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Build and Deployment
|
||||
|
||||
### Build Verification
|
||||
- [ ] **Maven build succeeds**: `mvn clean package` ✅
|
||||
- [ ] **All tests pass**: `mvn test` ✅
|
||||
- [ ] **Coverage check passes**: `mvn jacoco:check` ✅
|
||||
- [ ] **No build warnings** (deprecations, unchecked casts)
|
||||
|
||||
### CI/CD Pipeline
|
||||
- [ ] **CI pipeline passing** (all checks green)
|
||||
- [ ] **No new dependencies** (or dependencies documented below)
|
||||
- [ ] **Fat JAR builds correctly** (if applicable)
|
||||
|
||||
**New Dependencies** (if any):
|
||||
<!-- List new Maven dependencies -->
|
||||
```xml
|
||||
<!-- Example:
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>32.1.3-jre</version>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration/Upgrade Path
|
||||
|
||||
<!-- Only fill out if breaking changes -->
|
||||
|
||||
- [ ] **No breaking changes**
|
||||
- [ ] **Breaking changes documented** (describe below)
|
||||
- [ ] **Migration guide provided** (for users/operators)
|
||||
|
||||
**Breaking Changes**:
|
||||
```
|
||||
<!-- Describe breaking changes and migration steps -->
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots/Logs (if applicable)
|
||||
|
||||
<!-- Add screenshots for UI changes, logs for behavior changes -->
|
||||
|
||||
**Coverage Report**:
|
||||
<!-- Attach screenshot or link to JaCoCo HTML report -->
|
||||
|
||||
|
||||
**Test Results**:
|
||||
<!-- Attach screenshot or paste test output if relevant -->
|
||||
|
||||
|
||||
**Logs/Output**:
|
||||
<!-- Paste relevant log output if demonstrating behavior -->
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pre-Merge Checklist
|
||||
|
||||
<!-- Verify before requesting review -->
|
||||
|
||||
### Code Quality
|
||||
- [ ] **Self-reviewed code** (read through all changes)
|
||||
- [ ] **No commented-out code** (removed debug code)
|
||||
- [ ] **No debug statements** (removed `System.out.println`, excessive logging)
|
||||
- [ ] **Code formatted consistently** (IDE auto-format applied)
|
||||
|
||||
### Testing
|
||||
- [ ] **All tests passing locally** (`mvn test`)
|
||||
- [ ] **Coverage thresholds met** (95%/90%)
|
||||
- [ ] **Integration tests passing** (if applicable)
|
||||
|
||||
### Documentation
|
||||
- [ ] **Javadoc complete** (all public APIs)
|
||||
- [ ] **README updated** (if needed)
|
||||
- [ ] **Requirement traceability added** (in Javadoc)
|
||||
|
||||
### Process
|
||||
- [ ] **Feature branch up-to-date** with develop (rebased or merged)
|
||||
- [ ] **Conflicts resolved** (if any)
|
||||
- [ ] **CI pipeline green** (all checks passing)
|
||||
|
||||
---
|
||||
|
||||
## 👥 Reviewers
|
||||
|
||||
### Required Reviewers
|
||||
<!-- Tag specific reviewers if needed -->
|
||||
|
||||
- [ ] **Senior Developer**: @[username] (for architectural changes)
|
||||
- [ ] **QA Engineer**: @[username] (for test changes)
|
||||
- [ ] **Security Specialist**: @[username] (for security-sensitive code)
|
||||
|
||||
### Review Priority
|
||||
<!-- Check one -->
|
||||
- [ ] **Low Priority**: Minor change, no rush
|
||||
- [ ] **Normal Priority**: Standard feature/fix
|
||||
- [ ] **High Priority**: Blocking other work
|
||||
- [ ] **Critical Priority**: Production issue, needs immediate review
|
||||
|
||||
---
|
||||
|
||||
## 💬 Additional Notes
|
||||
|
||||
<!-- Any additional context, explanations, or questions for reviewers -->
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Issues/PRs
|
||||
|
||||
<!-- Link to related Gitea issues or PRs -->
|
||||
|
||||
- Closes #XX (issue number)
|
||||
- Related to #YY
|
||||
- Depends on #ZZ
|
||||
|
||||
---
|
||||
|
||||
## ✍️ Author Checklist
|
||||
|
||||
<!-- Final verification before submitting -->
|
||||
|
||||
- [ ] I have read the [Code Review Guidelines](CODE_REVIEW_GUIDELINES.md)
|
||||
- [ ] I have read the [TDD Compliance Checklist](TDD_COMPLIANCE_CHECKLIST.md)
|
||||
- [ ] I have followed TDD methodology (tests before code)
|
||||
- [ ] I have self-reviewed my code
|
||||
- [ ] I have tested all changes locally
|
||||
- [ ] I have updated documentation
|
||||
- [ ] I have added requirement traceability
|
||||
- [ ] I am ready for code review
|
||||
|
||||
---
|
||||
|
||||
**Submitted by**: @[your-username]
|
||||
**Date**: [YYYY-MM-DD]
|
||||
**Branch**: `feature/[branch-name]` → `develop` (or `main`)
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
REVIEWER INSTRUCTIONS:
|
||||
1. Review using the Code Review Guidelines checklist
|
||||
2. Verify TDD compliance (Git history, tests before code)
|
||||
3. Check hexagonal architecture boundaries
|
||||
4. Verify thread safety (if applicable)
|
||||
5. Check test coverage (≥95%/90%)
|
||||
6. Provide constructive feedback with categories (🚨 BLOCKER, ⚠️ MAJOR, 💡 SUGGESTION)
|
||||
7. Approve when all blockers resolved and checklist complete
|
||||
-->
|
||||
@@ -0,0 +1,474 @@
|
||||
# TDD Compliance Checklist
|
||||
**Version**: 1.0
|
||||
**Project**: HTTP Sender Plugin (HSP)
|
||||
**Last Updated**: 2025-11-20
|
||||
|
||||
## Purpose
|
||||
|
||||
This checklist ensures that all development follows Test-Driven Development (TDD) methodology as mandated by the project implementation plan. **ALL code MUST be developed using the Red-Green-Refactor cycle**.
|
||||
|
||||
---
|
||||
|
||||
## 🚨 TDD Non-Negotiable Rules
|
||||
|
||||
### Rule 1: Tests First, Code Second
|
||||
- [ ] **No production code written without a failing test**
|
||||
- [ ] Test defines the interface and expected behavior
|
||||
- [ ] Implementation satisfies the test requirements
|
||||
- [ ] Test is committed to Git BEFORE implementation
|
||||
|
||||
### Rule 2: Red-Green-Refactor Cycle Documented
|
||||
- [ ] **RED**: Failing test committed with message `test: description (RED)`
|
||||
- [ ] **GREEN**: Minimal implementation committed with message `feat: description (GREEN)`
|
||||
- [ ] **REFACTOR**: Code improvements committed with message `refactor: description`
|
||||
- [ ] Git history clearly shows TDD cycle for each feature
|
||||
|
||||
### Rule 3: All Tests Must Pass
|
||||
- [ ] Never commit with broken tests
|
||||
- [ ] CI pipeline must be green
|
||||
- [ ] Fix build breaks immediately (within 15 minutes)
|
||||
- [ ] All existing tests pass before adding new features
|
||||
|
||||
### Rule 4: Coverage Thresholds Mandatory
|
||||
- [ ] **95% line coverage minimum** (enforced by JaCoCo)
|
||||
- [ ] **90% branch coverage minimum** (enforced by JaCoCo)
|
||||
- [ ] CI pipeline fails if coverage drops below threshold
|
||||
- [ ] Coverage trends tracked in sprint metrics
|
||||
|
||||
---
|
||||
|
||||
## 📋 Pre-Commit Checklist
|
||||
|
||||
Before committing any code, verify:
|
||||
|
||||
### Test Verification
|
||||
- [ ] Tests written BEFORE implementation
|
||||
- [ ] Tests follow AAA pattern (Arrange-Act-Assert)
|
||||
- [ ] Test names clearly describe behavior: `shouldDoX_whenY()`
|
||||
- [ ] Tests are independent (no shared state between tests)
|
||||
- [ ] Tests are repeatable (same result every time)
|
||||
- [ ] Tests are fast (unit tests < 100ms each)
|
||||
|
||||
### Git Commit Verification
|
||||
- [ ] RED commit exists (failing test)
|
||||
- [ ] GREEN commit follows RED (passing implementation)
|
||||
- [ ] REFACTOR commit (if applicable)
|
||||
- [ ] Commit messages follow TDD pattern
|
||||
- [ ] Multiple commits per day (frequent integration)
|
||||
|
||||
### Code Coverage Verification
|
||||
- [ ] JaCoCo report generated: `mvn jacoco:report`
|
||||
- [ ] Line coverage ≥ 95%
|
||||
- [ ] Branch coverage ≥ 90%
|
||||
- [ ] No uncovered critical paths
|
||||
- [ ] Coverage report reviewed in IDE
|
||||
|
||||
### Test Quality Verification
|
||||
- [ ] Tests actually fail when they should (verify RED phase)
|
||||
- [ ] Tests pass for correct reasons (not false positives)
|
||||
- [ ] Edge cases covered (boundary conditions)
|
||||
- [ ] Error scenarios tested (exceptions, failures)
|
||||
- [ ] Happy path and sad path both tested
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Pull Request TDD Compliance Review
|
||||
|
||||
### Git History Review
|
||||
|
||||
**Requirement**: Every PR must show clear TDD cycle in commits
|
||||
|
||||
Check commit history for pattern:
|
||||
```
|
||||
✓ test: add BufferManager offer() test (RED)
|
||||
✓ feat: implement BufferManager offer() method (GREEN)
|
||||
✓ refactor: add javadoc to BufferManager
|
||||
✓ test: add BufferManager overflow test (RED)
|
||||
✓ feat: implement FIFO overflow handling (GREEN)
|
||||
✓ refactor: improve naming in overflow logic
|
||||
```
|
||||
|
||||
**Red Flags**:
|
||||
```
|
||||
✗ feat: implement entire BufferManager (no tests first)
|
||||
✗ test: add tests for BufferManager (tests after implementation)
|
||||
✗ test + feat: implement BufferManager with tests (combined commit)
|
||||
```
|
||||
|
||||
### Test-to-Code Commit Ratio
|
||||
|
||||
- [ ] Approximately 1:1 ratio of test commits to implementation commits
|
||||
- [ ] Tests consistently appear BEFORE implementations in history
|
||||
- [ ] No large blocks of code without corresponding tests
|
||||
- [ ] Refactor commits are smaller, incremental improvements
|
||||
|
||||
### Coverage Report Review
|
||||
|
||||
Reviewer must verify:
|
||||
- [ ] JaCoCo report attached to PR or accessible in CI
|
||||
- [ ] Coverage meets 95%/90% thresholds
|
||||
- [ ] No suspicious untested code paths
|
||||
- [ ] New code covered by new tests (not just existing tests)
|
||||
|
||||
### Test Quality Review
|
||||
|
||||
- [ ] Tests follow AAA pattern consistently
|
||||
- [ ] Test names are descriptive and behavior-focused
|
||||
- [ ] Tests use appropriate assertions (not just `assertTrue`)
|
||||
- [ ] Mock objects used appropriately (not over-mocking)
|
||||
- [ ] Integration tests cover inter-component boundaries
|
||||
|
||||
---
|
||||
|
||||
## 📊 TDD Metrics Dashboard
|
||||
|
||||
### Weekly Metrics to Track
|
||||
|
||||
| Metric | Target | How to Measure |
|
||||
|--------|--------|----------------|
|
||||
| Test-to-Code Commit Ratio | 1:1 | Count test commits vs implementation commits |
|
||||
| Line Coverage | ≥95% | JaCoCo report |
|
||||
| Branch Coverage | ≥90% | JaCoCo report |
|
||||
| Mutation Score | ≥75% | PIT mutation testing |
|
||||
| Unit Test Execution Time | <5 min | CI pipeline logs |
|
||||
| Flaky Test Rate | <1% | Track test failures/re-runs |
|
||||
| TDD Compliance Rate | 100% | PR reviews with TDD violations |
|
||||
|
||||
### Sprint Retrospective TDD Questions
|
||||
|
||||
1. **Did we follow TDD for all code this sprint?**
|
||||
- If no, what were the exceptions and why?
|
||||
|
||||
2. **Where did we skip tests first?**
|
||||
- Identify patterns and root causes
|
||||
|
||||
3. **What slowed down our TDD workflow?**
|
||||
- Tool issues, environment problems, knowledge gaps?
|
||||
|
||||
4. **How can we improve TDD practices next sprint?**
|
||||
- Training needs, tooling improvements, pair programming?
|
||||
|
||||
---
|
||||
|
||||
## 🧪 TDD by Component Type
|
||||
|
||||
### Port Interfaces (Test-First Design)
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Test written defining interface contract FIRST
|
||||
- [ ] Test shows expected method signatures and return types
|
||||
- [ ] Interface defined to satisfy test
|
||||
- [ ] Mock implementation created for testing
|
||||
- [ ] Adapter implementation follows with its own TDD cycle
|
||||
|
||||
**Example Test (RED)**:
|
||||
```java
|
||||
@Test
|
||||
void shouldPollEndpoint_whenUrlProvided() {
|
||||
// Given
|
||||
IHttpPollingPort httpPort = new HttpPollingAdapter(config);
|
||||
String url = "http://example.com/data";
|
||||
|
||||
// When
|
||||
CompletableFuture<byte[]> result = httpPort.pollEndpoint(url);
|
||||
|
||||
// Then
|
||||
assertThat(result).isCompletedWithValue(expectedData);
|
||||
}
|
||||
```
|
||||
|
||||
### Domain Models (Value Object TDD)
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Test immutability (no setters, final fields)
|
||||
- [ ] Test equality (equals/hashCode contract)
|
||||
- [ ] Test serialization (JSON, Base64 encoding)
|
||||
- [ ] Test validation (constructor throws on invalid data)
|
||||
- [ ] Test thread safety (concurrent access if applicable)
|
||||
|
||||
**Example Test (RED)**:
|
||||
```java
|
||||
@Test
|
||||
void shouldBeImmutable_whenCreated() {
|
||||
// Given
|
||||
DiagnosticData data1 = new DiagnosticData("url", new byte[]{1,2,3});
|
||||
DiagnosticData data2 = new DiagnosticData("url", new byte[]{1,2,3});
|
||||
|
||||
// Then
|
||||
assertThat(data1).isEqualTo(data2);
|
||||
assertThat(data1).isNotSameAs(data2);
|
||||
// Verify no setters exist (compilation check)
|
||||
}
|
||||
```
|
||||
|
||||
### Core Services (Business Logic TDD)
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Test business rules and invariants
|
||||
- [ ] Test orchestration logic (calls to ports)
|
||||
- [ ] Test error handling and exceptions
|
||||
- [ ] Test statistics and monitoring
|
||||
- [ ] Test concurrency if applicable
|
||||
- [ ] Use mocks for port dependencies
|
||||
|
||||
**Example Test (RED)**:
|
||||
```java
|
||||
@Test
|
||||
void shouldRejectOversizedData_whenFileExceeds1MB() {
|
||||
// Given
|
||||
DataCollectionService service = new DataCollectionService(httpPort, bufferPort);
|
||||
byte[] largeData = new byte[2_000_000]; // 2 MB
|
||||
|
||||
// When / Then
|
||||
assertThatThrownBy(() -> service.validateData(largeData, "http://test"))
|
||||
.isInstanceOf(OversizedDataException.class)
|
||||
.hasMessageContaining("1MB");
|
||||
}
|
||||
```
|
||||
|
||||
### Adapters (Infrastructure TDD)
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Unit tests with mocks for quick feedback
|
||||
- [ ] Integration tests with real infrastructure (WireMock, gRPC test server)
|
||||
- [ ] Test retry logic and backoff strategies
|
||||
- [ ] Test timeouts and error scenarios
|
||||
- [ ] Test thread safety and concurrency
|
||||
- [ ] Test resource cleanup (connections, streams)
|
||||
|
||||
**Example Test (RED)**:
|
||||
```java
|
||||
@Test
|
||||
void shouldRetryThreeTimes_whenHttpFails() {
|
||||
// Given
|
||||
stubFor(get("/endpoint").willReturn(aResponse().withStatus(500)));
|
||||
HttpPollingAdapter adapter = new HttpPollingAdapter(config);
|
||||
|
||||
// When
|
||||
assertThatThrownBy(() -> adapter.pollEndpoint(url).join())
|
||||
.hasCauseInstanceOf(PollingFailedException.class);
|
||||
|
||||
// Then (Req-FR-17: 3 retries)
|
||||
verify(3, getRequestedFor(urlEqualTo("/endpoint")));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Daily TDD Workflow
|
||||
|
||||
### Morning (Start of Day)
|
||||
|
||||
1. [ ] Pull latest from main/develop branch
|
||||
2. [ ] Review overnight CI builds (all green?)
|
||||
3. [ ] Check JaCoCo coverage report (≥95%/90%?)
|
||||
4. [ ] Pick next user story from sprint backlog
|
||||
5. [ ] Create feature branch: `git checkout -b feature/buffer-manager`
|
||||
6. [ ] Review requirements and acceptance criteria
|
||||
|
||||
### During Development (5-10 TDD Cycles per Day)
|
||||
|
||||
**Per Feature/Method**:
|
||||
|
||||
1. [ ] **Write Test (RED)**: 15-30 minutes
|
||||
- Write failing test for next requirement
|
||||
- Run test, verify it fails: `mvn test -Dtest=ClassName#testMethod`
|
||||
- Commit: `git commit -m "test: add test for X (RED)"`
|
||||
|
||||
2. [ ] **Write Code (GREEN)**: 15-45 minutes
|
||||
- Write minimal code to make test pass
|
||||
- Run test, verify it passes: `mvn test`
|
||||
- Run all tests, verify no regressions: `mvn test`
|
||||
- Commit: `git commit -m "feat: implement X (GREEN)"`
|
||||
|
||||
3. [ ] **Refactor**: 10-20 minutes (if needed)
|
||||
- Improve code quality, remove duplication
|
||||
- Run all tests, verify still passing: `mvn test`
|
||||
- Commit: `git commit -m "refactor: improve X naming/structure"`
|
||||
|
||||
4. [ ] **Verify Coverage**: 5 minutes
|
||||
- Generate coverage: `mvn jacoco:report`
|
||||
- Check coverage in IDE or HTML report
|
||||
- Ensure new code is covered
|
||||
|
||||
5. [ ] **Push Frequently**: Every 2-3 cycles
|
||||
- Push to remote: `git push origin feature/buffer-manager`
|
||||
- Verify CI pipeline runs and passes
|
||||
|
||||
### End of Day
|
||||
|
||||
1. [ ] Push feature branch: `git push origin feature/buffer-manager`
|
||||
2. [ ] Create pull request in Gitea (if feature complete)
|
||||
3. [ ] Verify CI pipeline passes (green build)
|
||||
4. [ ] Request code review from team member
|
||||
5. [ ] Update sprint board (move tasks to "In Review")
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ TDD Anti-Patterns to Avoid
|
||||
|
||||
### 1. Writing Code Before Tests
|
||||
```
|
||||
✗ WRONG:
|
||||
- Implement entire BufferManager class
|
||||
- Then write tests to cover it
|
||||
|
||||
✓ CORRECT:
|
||||
- Write test for offer() method
|
||||
- Implement offer() method
|
||||
- Write test for poll() method
|
||||
- Implement poll() method
|
||||
```
|
||||
|
||||
### 2. Testing Implementation Details
|
||||
```
|
||||
✗ WRONG:
|
||||
@Test
|
||||
void shouldUseArrayBlockingQueue_inBufferManager() {
|
||||
// Testing internal implementation choice
|
||||
}
|
||||
|
||||
✓ CORRECT:
|
||||
@Test
|
||||
void shouldStoreFIFO_whenMultipleOffersAndPolls() {
|
||||
// Testing observable behavior
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Over-Mocking
|
||||
```
|
||||
✗ WRONG:
|
||||
@Test
|
||||
void shouldCalculateSum() {
|
||||
Calculator calc = mock(Calculator.class);
|
||||
when(calc.add(2, 3)).thenReturn(5);
|
||||
assertThat(calc.add(2, 3)).isEqualTo(5); // Circular mocking
|
||||
}
|
||||
|
||||
✓ CORRECT:
|
||||
@Test
|
||||
void shouldCalculateSum() {
|
||||
Calculator calc = new Calculator(); // Real object
|
||||
assertThat(calc.add(2, 3)).isEqualTo(5);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Flaky Tests
|
||||
```
|
||||
✗ WRONG:
|
||||
@Test
|
||||
void shouldComplete_withinReasonableTime() {
|
||||
Thread.sleep(100); // Time-dependent test
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
✓ CORRECT:
|
||||
@Test
|
||||
void shouldComplete_whenDataAvailable() {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
// Use proper synchronization primitives
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Large, Monolithic Tests
|
||||
```
|
||||
✗ WRONG:
|
||||
@Test
|
||||
void shouldTestEntireSystem() {
|
||||
// 200 lines of test code testing everything
|
||||
}
|
||||
|
||||
✓ CORRECT:
|
||||
@Test
|
||||
void shouldPollEndpoint_whenUrlValid() { /* 10 lines */ }
|
||||
|
||||
@Test
|
||||
void shouldRetryOnFailure_whenHttpError() { /* 10 lines */ }
|
||||
|
||||
@Test
|
||||
void shouldStoreInBuffer_whenDataReceived() { /* 10 lines */ }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 TDD Resources
|
||||
|
||||
### Internal Documentation
|
||||
- [Project Implementation Plan](../PROJECT_IMPLEMENTATION_PLAN.md) - TDD Section (lines 644-880)
|
||||
- [Test Strategy](../testing/test-strategy.md) - Testing approach
|
||||
- [Architecture Decisions](../ARCHITECTURE_DECISIONS.md) - Design rationale
|
||||
|
||||
### TDD Training Materials
|
||||
- Kent Beck - "Test-Driven Development by Example"
|
||||
- Martin Fowler - "Refactoring: Improving the Design of Existing Code"
|
||||
- Uncle Bob Martin - "Clean Code" (Chapter on Unit Tests)
|
||||
|
||||
### Tools and IDE Setup
|
||||
- **IntelliJ IDEA**: Enable "Run tests on save" for instant feedback
|
||||
- **Eclipse**: Install EclEmma for coverage visualization
|
||||
- **JaCoCo**: Maven plugin for coverage enforcement
|
||||
- **PIT**: Mutation testing for test quality validation
|
||||
- **WireMock**: HTTP mocking for integration tests
|
||||
- **gRPC Testing**: Mock gRPC servers for integration tests
|
||||
|
||||
---
|
||||
|
||||
## ✅ Definition of Done (TDD Perspective)
|
||||
|
||||
A user story/task is considered DONE when:
|
||||
|
||||
- [ ] All tests written BEFORE implementation (verified in Git history)
|
||||
- [ ] All tests pass (green CI build)
|
||||
- [ ] Line coverage ≥ 95%, branch coverage ≥ 90%
|
||||
- [ ] Code review completed with TDD compliance verified
|
||||
- [ ] No TDD violations found in PR review
|
||||
- [ ] Git history shows clear RED-GREEN-REFACTOR cycles
|
||||
- [ ] Integration tests pass (if applicable)
|
||||
- [ ] Documentation updated (Javadoc, README)
|
||||
- [ ] Merged to develop branch
|
||||
|
||||
---
|
||||
|
||||
## 📞 TDD Support and Questions
|
||||
|
||||
### Who to Ask
|
||||
- **TDD Questions**: Tech Lead, Senior Developers
|
||||
- **Tool Setup**: DevOps Engineer
|
||||
- **Coverage Issues**: QA Lead
|
||||
- **Git Workflow**: Tech Lead
|
||||
|
||||
### Pair Programming Sessions
|
||||
- **Daily TDD Pairs**: Rotate pairs daily for knowledge sharing
|
||||
- **TDD Mob Sessions**: Weekly mob programming on complex TDD scenarios
|
||||
- **TDD Code Reviews**: All PRs require TDD compliance review
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Summary: The TDD Mindset
|
||||
|
||||
> **"If it's worth building, it's worth testing. If it's not worth testing, why are you wasting your time working on it?"**
|
||||
|
||||
**TDD is not optional—it's how we build software at HSP.**
|
||||
|
||||
### The TDD Promise
|
||||
- Tests document behavior
|
||||
- Refactoring is safe
|
||||
- Bugs are caught early
|
||||
- Code is more modular
|
||||
- Coverage is not a goal—it's a side effect
|
||||
|
||||
### The TDD Reality Check
|
||||
If you find yourself:
|
||||
- Writing code without tests → STOP
|
||||
- Committing untested code → STOP
|
||||
- Skipping tests "just this once" → STOP
|
||||
|
||||
**Return to RED-GREEN-REFACTOR. Always.**
|
||||
|
||||
---
|
||||
|
||||
**Document Control**:
|
||||
- Version: 1.0
|
||||
- Created: 2025-11-20
|
||||
- Status: Active
|
||||
- Review Cycle: After each sprint
|
||||
@@ -0,0 +1,725 @@
|
||||
# Thread Safety Guidelines
|
||||
**Version**: 1.0
|
||||
**Project**: HTTP Sender Plugin (HSP)
|
||||
**Last Updated**: 2025-11-20
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines thread safety requirements, patterns, and best practices for the HSP project. The HSP system uses **Java 25 Virtual Threads** for concurrent HTTP polling and must ensure thread-safe operations for shared state.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Thread Safety Requirements
|
||||
|
||||
### 1. Critical Thread-Safe Components
|
||||
|
||||
The following components **MUST be thread-safe** per requirements:
|
||||
|
||||
| Component | Requirement | Concurrency Pattern | Justification |
|
||||
|-----------|-------------|---------------------|---------------|
|
||||
| **BufferManager** | Req-FR-26, Arch-7 | Thread-safe queue | Multiple producers (HTTP pollers), single consumer (gRPC transmitter) |
|
||||
| **CollectionStatistics** | Req-NFR-8, Arch-8 | Atomic counters | Multiple threads updating statistics concurrently |
|
||||
| **DataCollectionService** | Req-FR-14, Arch-6 | Virtual threads | 1000 concurrent HTTP polling tasks |
|
||||
| **RateLimiter** | Enhancement | Thread-safe rate limiting | Multiple threads requesting rate limit permits |
|
||||
| **BackpressureController** | Req-FR-27 | Atomic monitoring | Buffer usage checked by multiple threads |
|
||||
|
||||
### 2. Immutable Components (Thread-Safe by Design)
|
||||
|
||||
The following components **MUST be immutable**:
|
||||
|
||||
| Component | Type | Thread Safety |
|
||||
|-----------|------|---------------|
|
||||
| **DiagnosticData** | Value Object | Immutable (final fields, no setters) |
|
||||
| **Configuration** | Value Object | Immutable (loaded once at startup) |
|
||||
| **HealthCheckResponse** | Value Object | Immutable (snapshot of current state) |
|
||||
| **BufferStatistics** | Value Object | Immutable (snapshot of buffer state) |
|
||||
|
||||
### 3. Single-Threaded Components (No Thread Safety Needed)
|
||||
|
||||
The following components run on **dedicated threads** (no concurrent access):
|
||||
|
||||
| Component | Thread Model | Justification |
|
||||
|-----------|-------------|---------------|
|
||||
| **DataTransmissionService** | Single consumer thread | Req-FR-25: One thread consumes from buffer |
|
||||
| **ConfigurationManager** | Startup only | Loaded once before concurrent operations start |
|
||||
| **Adapters** | Per-request isolation | Each request creates new adapter instance or uses thread-local state |
|
||||
|
||||
---
|
||||
|
||||
## 🧵 Concurrency Model Overview
|
||||
|
||||
### System Threading Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ HTTP Sender Plugin (HSP) Threading Model │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Main Thread │
|
||||
│ └─> Startup & Configuration │
|
||||
│ │
|
||||
│ Virtual Thread Pool (HTTP Polling) │
|
||||
│ ├─> Virtual Thread 1 → HttpPollingAdapter │
|
||||
│ ├─> Virtual Thread 2 → HttpPollingAdapter │
|
||||
│ ├─> Virtual Thread 3 → HttpPollingAdapter │
|
||||
│ └─> ... (up to 1000 concurrent virtual threads) │
|
||||
│ ↓ │
|
||||
│ [Thread-Safe BufferManager] (ArrayBlockingQueue) │
|
||||
│ ↓ │
|
||||
│ Single Consumer Thread (gRPC Transmission) │
|
||||
│ └─> DataTransmissionService → GrpcStreamAdapter │
|
||||
│ │
|
||||
│ Health Check HTTP Server Thread │
|
||||
│ └─> HealthCheckController (embedded Jetty) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Virtual Threads (Java 25)
|
||||
|
||||
**Why Virtual Threads?**
|
||||
- **Requirement**: Req-NFR-1: Support 1000 concurrent endpoints
|
||||
- **Benefit**: Lightweight threads (millions possible vs. thousands of platform threads)
|
||||
- **Use Case**: I/O-bound HTTP polling (mostly waiting for network responses)
|
||||
|
||||
**Virtual Thread Best Practices**:
|
||||
- ✅ **DO**: Use for I/O-bound tasks (HTTP requests, file I/O)
|
||||
- ✅ **DO**: Create one virtual thread per endpoint poll
|
||||
- ✅ **DO**: Let virtual threads block (don't use async APIs unnecessarily)
|
||||
- ❌ **DON'T**: Use for CPU-bound tasks (use platform threads instead)
|
||||
- ❌ **DON'T**: Use with `synchronized` on long-running operations (use `ReentrantLock`)
|
||||
|
||||
**Creating Virtual Threads**:
|
||||
```java
|
||||
// CORRECT: Virtual thread executor for HTTP polling
|
||||
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
// Schedule polling tasks
|
||||
for (String url : endpoints) {
|
||||
executor.submit(() -> pollEndpoint(url));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Thread Safety Patterns
|
||||
|
||||
### Pattern 1: Immutability (Preferred)
|
||||
|
||||
**When to Use**: Value objects, configuration, data transfer objects
|
||||
|
||||
**Benefits**:
|
||||
- Thread-safe by design (no synchronization needed)
|
||||
- No defensive copying required
|
||||
- Easier to reason about
|
||||
|
||||
**Implementation**:
|
||||
```java
|
||||
/**
|
||||
* Immutable value object representing diagnostic data.
|
||||
* Thread-safe by design (immutable).
|
||||
*
|
||||
* @requirement Req-FR-22 Immutable data representation
|
||||
*/
|
||||
public final class DiagnosticData {
|
||||
private final String endpointUrl;
|
||||
private final byte[] data;
|
||||
private final Instant timestamp;
|
||||
|
||||
public DiagnosticData(String endpointUrl, byte[] data) {
|
||||
this.endpointUrl = Objects.requireNonNull(endpointUrl);
|
||||
// Defensive copy of mutable array
|
||||
this.data = Arrays.copyOf(data, data.length);
|
||||
this.timestamp = Instant.now();
|
||||
}
|
||||
|
||||
// Only getters, no setters
|
||||
public String getEndpointUrl() {
|
||||
return endpointUrl;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
// Return defensive copy
|
||||
return Arrays.copyOf(data, data.length);
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp; // Instant is immutable
|
||||
}
|
||||
|
||||
// equals, hashCode, toString...
|
||||
}
|
||||
```
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Class declared `final` (cannot be subclassed)
|
||||
- [ ] All fields declared `final` (assigned once in constructor)
|
||||
- [ ] No setter methods (only getters)
|
||||
- [ ] Defensive copies for mutable fields (arrays, collections)
|
||||
- [ ] Getters return defensive copies of mutable fields
|
||||
|
||||
### Pattern 2: Concurrent Collections
|
||||
|
||||
**When to Use**: Shared data structures accessed by multiple threads
|
||||
|
||||
**Preferred Collections**:
|
||||
- `ArrayBlockingQueue<T>`: Fixed-size blocking queue (buffer)
|
||||
- `ConcurrentHashMap<K,V>`: Thread-safe map (if needed)
|
||||
- `CopyOnWriteArrayList<T>`: Thread-safe list for read-heavy workloads
|
||||
|
||||
**Implementation (BufferManager)**:
|
||||
```java
|
||||
/**
|
||||
* Thread-safe buffer manager using ArrayBlockingQueue.
|
||||
*
|
||||
* <p>Supports multiple producers (HTTP polling threads) and single
|
||||
* consumer (gRPC transmission thread).
|
||||
*
|
||||
* @requirement Req-FR-26 Thread-safe buffer
|
||||
* @requirement Req-Arch-7 Concurrent collection usage
|
||||
*/
|
||||
public class BufferManager {
|
||||
private final BlockingQueue<DiagnosticData> buffer;
|
||||
private final int capacity;
|
||||
|
||||
// Statistics (atomic counters)
|
||||
private final AtomicLong totalOffered = new AtomicLong(0);
|
||||
private final AtomicLong totalDiscarded = new AtomicLong(0);
|
||||
|
||||
public BufferManager(int capacity) {
|
||||
this.capacity = capacity;
|
||||
// ArrayBlockingQueue is thread-safe
|
||||
this.buffer = new ArrayBlockingQueue<>(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offers data to buffer. Thread-safe.
|
||||
* Discards oldest if buffer is full (FIFO).
|
||||
*
|
||||
* @requirement Req-FR-27 FIFO overflow handling
|
||||
*/
|
||||
public void offer(DiagnosticData data) {
|
||||
Objects.requireNonNull(data, "data cannot be null");
|
||||
totalOffered.incrementAndGet();
|
||||
|
||||
if (!buffer.offer(data)) {
|
||||
// Buffer full, discard oldest (FIFO)
|
||||
buffer.poll(); // Remove oldest
|
||||
buffer.offer(data); // Add new
|
||||
totalDiscarded.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls data from buffer. Thread-safe.
|
||||
* Blocks if buffer is empty (up to timeout).
|
||||
*/
|
||||
public DiagnosticData poll(long timeout, TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
return buffer.poll(timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current buffer size. Thread-safe.
|
||||
*/
|
||||
public int size() {
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns buffer statistics snapshot. Thread-safe.
|
||||
*/
|
||||
public BufferStatistics getStatistics() {
|
||||
return new BufferStatistics(
|
||||
size(),
|
||||
capacity,
|
||||
totalOffered.get(),
|
||||
totalDiscarded.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Use `BlockingQueue` for producer-consumer patterns
|
||||
- [ ] Use `ArrayBlockingQueue` for bounded buffers
|
||||
- [ ] Use `ConcurrentHashMap` for thread-safe maps
|
||||
- [ ] Avoid `synchronized` on collection itself (use concurrent collection)
|
||||
|
||||
### Pattern 3: Atomic Variables
|
||||
|
||||
**When to Use**: Counters, flags, simple shared state
|
||||
|
||||
**Atomic Classes**:
|
||||
- `AtomicInteger`: Thread-safe integer counter
|
||||
- `AtomicLong`: Thread-safe long counter
|
||||
- `AtomicBoolean`: Thread-safe boolean flag
|
||||
- `AtomicReference<T>`: Thread-safe object reference
|
||||
|
||||
**Implementation (CollectionStatistics)**:
|
||||
```java
|
||||
/**
|
||||
* Thread-safe collection statistics using atomic variables.
|
||||
*
|
||||
* @requirement Req-NFR-8 Statistics tracking
|
||||
* @requirement Req-Arch-8 Atomic operations
|
||||
*/
|
||||
public class CollectionStatistics {
|
||||
// Atomic counters for thread safety
|
||||
private final AtomicLong totalPolls = new AtomicLong(0);
|
||||
private final AtomicLong successfulPolls = new AtomicLong(0);
|
||||
private final AtomicLong failedPolls = new AtomicLong(0);
|
||||
|
||||
// Time-windowed metrics (last 30 seconds)
|
||||
private final Queue<Long> recentPolls = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Increments total poll count. Thread-safe.
|
||||
*/
|
||||
public void incrementTotalPolls() {
|
||||
long count = totalPolls.incrementAndGet();
|
||||
recentPolls.offer(System.currentTimeMillis());
|
||||
cleanupOldMetrics();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments successful poll count. Thread-safe.
|
||||
*/
|
||||
public void incrementSuccessfulPolls() {
|
||||
successfulPolls.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments failed poll count. Thread-safe.
|
||||
*/
|
||||
public void incrementFailedPolls() {
|
||||
failedPolls.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns snapshot of current statistics. Thread-safe.
|
||||
*/
|
||||
public StatisticsSnapshot getSnapshot() {
|
||||
return new StatisticsSnapshot(
|
||||
totalPolls.get(),
|
||||
successfulPolls.get(),
|
||||
failedPolls.get(),
|
||||
calculateRecentRate()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes metrics older than 30 seconds.
|
||||
*/
|
||||
private void cleanupOldMetrics() {
|
||||
long cutoff = System.currentTimeMillis() - 30_000;
|
||||
recentPolls.removeIf(timestamp -> timestamp < cutoff);
|
||||
}
|
||||
|
||||
private double calculateRecentRate() {
|
||||
return recentPolls.size() / 30.0; // polls per second
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Use `AtomicLong` for counters (not `long` with `synchronized`)
|
||||
- [ ] Use `incrementAndGet()` for atomic increment-and-read
|
||||
- [ ] Use `get()` for atomic read
|
||||
- [ ] Use `compareAndSet()` for atomic compare-and-swap (if needed)
|
||||
|
||||
### Pattern 4: Locks (When Needed)
|
||||
|
||||
**When to Use**: Complex synchronized operations, multiple state updates
|
||||
|
||||
**Lock Types**:
|
||||
- `ReentrantLock`: Exclusive lock (mutual exclusion)
|
||||
- `ReentrantReadWriteLock`: Read-write lock (multiple readers, one writer)
|
||||
- `StampedLock`: Optimistic locking (Java 8+)
|
||||
|
||||
**Implementation (if needed)**:
|
||||
```java
|
||||
public class RateLimitedAdapter {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private long lastRequestTime = 0;
|
||||
private final long minIntervalMs;
|
||||
|
||||
/**
|
||||
* Thread-safe rate limiting with explicit lock.
|
||||
*
|
||||
* Prefer ReentrantLock over synchronized for virtual threads.
|
||||
*/
|
||||
public void acquirePermit() throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsed = now - lastRequestTime;
|
||||
if (elapsed < minIntervalMs) {
|
||||
Thread.sleep(minIntervalMs - elapsed);
|
||||
}
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
} finally {
|
||||
lock.unlock(); // ALWAYS unlock in finally
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Use `ReentrantLock` instead of `synchronized` for virtual threads
|
||||
- [ ] Always unlock in `finally` block
|
||||
- [ ] Avoid holding locks during I/O operations (risk of pinning virtual threads)
|
||||
- [ ] Document lock ordering if multiple locks used (prevent deadlock)
|
||||
|
||||
### Pattern 5: Thread Confinement
|
||||
|
||||
**When to Use**: State that doesn't need to be shared
|
||||
|
||||
**Strategies**:
|
||||
- **Stack Confinement**: Use local variables (method parameters, local vars)
|
||||
- **Thread-Local**: Use `ThreadLocal<T>` for thread-specific state
|
||||
- **Instance-Per-Thread**: Create new instances per thread
|
||||
|
||||
**Implementation**:
|
||||
```java
|
||||
/**
|
||||
* HttpPollingAdapter is thread-confined by design.
|
||||
* Each virtual thread creates its own adapter instance.
|
||||
* No shared mutable state → no thread safety needed.
|
||||
*/
|
||||
public class HttpPollingAdapter implements IHttpPollingPort {
|
||||
// Immutable configuration (thread-safe)
|
||||
private final Configuration config;
|
||||
|
||||
// Thread-confined HttpClient (one per instance)
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpPollingAdapter(Configuration config) {
|
||||
this.config = config; // Immutable
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(30))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<byte[]> pollEndpoint(String url) {
|
||||
// This method is called by a single virtual thread
|
||||
// No shared mutable state → thread-safe by design
|
||||
return httpClient.sendAsync(buildRequest(url), BodyHandlers.ofByteArray())
|
||||
.thenApply(HttpResponse::body);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Checklist**:
|
||||
- [ ] Prefer immutability and thread confinement over synchronization
|
||||
- [ ] Document thread ownership in Javadoc
|
||||
- [ ] Avoid sharing mutable state when possible
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Thread Safety
|
||||
|
||||
### Test Strategy
|
||||
|
||||
**1. Unit Tests with Concurrent Access**:
|
||||
```java
|
||||
@Test
|
||||
void shouldBeThreadSafe_whenMultipleThreadsOfferConcurrently() {
|
||||
// Given
|
||||
BufferManager buffer = new BufferManager(100);
|
||||
int numThreads = 50;
|
||||
int offersPerThread = 100;
|
||||
|
||||
// When: Multiple threads offer concurrently
|
||||
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
futures.add(executor.submit(() -> {
|
||||
for (int j = 0; j < offersPerThread; j++) {
|
||||
buffer.offer(new DiagnosticData("url", new byte[]{1,2,3}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait for completion
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
executor.shutdown();
|
||||
|
||||
// Then: All offers processed (no data loss except overflow)
|
||||
BufferStatistics stats = buffer.getStatistics();
|
||||
assertThat(stats.totalOffered()).isEqualTo(numThreads * offersPerThread);
|
||||
}
|
||||
```
|
||||
|
||||
**2. Stress Tests**:
|
||||
```java
|
||||
@Test
|
||||
void shouldHandleHighConcurrency_with1000ProducersAndConsumers() {
|
||||
BufferManager buffer = new BufferManager(300);
|
||||
|
||||
// 1000 producers
|
||||
ExecutorService producers = Executors.newVirtualThreadPerTaskExecutor();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
producers.submit(() -> {
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
buffer.offer(new DiagnosticData("url", new byte[]{1,2,3}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 1 consumer
|
||||
ExecutorService consumer = Executors.newSingleThreadExecutor();
|
||||
AtomicLong consumed = new AtomicLong(0);
|
||||
consumer.submit(() -> {
|
||||
while (consumed.get() < 1_000_000) {
|
||||
try {
|
||||
DiagnosticData data = buffer.poll(1, TimeUnit.SECONDS);
|
||||
if (data != null) {
|
||||
consumed.incrementAndGet();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for completion and verify
|
||||
producers.shutdown();
|
||||
producers.awaitTermination(10, TimeUnit.MINUTES);
|
||||
consumer.shutdown();
|
||||
consumer.awaitTermination(1, TimeUnit.MINUTES);
|
||||
|
||||
// Verify: No deadlock, no data corruption
|
||||
assertThat(consumed.get()).isGreaterThan(0);
|
||||
}
|
||||
```
|
||||
|
||||
**3. Race Condition Detection**:
|
||||
```java
|
||||
@Test
|
||||
void shouldNotHaveRaceCondition_inAtomicIncrement() {
|
||||
CollectionStatistics stats = new CollectionStatistics();
|
||||
int numThreads = 100;
|
||||
int incrementsPerThread = 10000;
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
executor.submit(() -> {
|
||||
for (int j = 0; j < incrementsPerThread; j++) {
|
||||
stats.incrementTotalPolls();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(1, TimeUnit.MINUTES);
|
||||
|
||||
// Verify: Exact count (no lost updates)
|
||||
assertThat(stats.getSnapshot().totalPolls())
|
||||
.isEqualTo((long) numThreads * incrementsPerThread);
|
||||
}
|
||||
```
|
||||
|
||||
### Thread Safety Test Checklist
|
||||
|
||||
- [ ] **Concurrent access tests**: Multiple threads accessing shared state
|
||||
- [ ] **Stress tests**: 100+ threads, 1000+ operations per thread
|
||||
- [ ] **Race condition tests**: Verify atomic operations (no lost updates)
|
||||
- [ ] **Deadlock tests**: Complex locking scenarios (if applicable)
|
||||
- [ ] **Immutability tests**: Verify no setters, defensive copies
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Common Thread Safety Mistakes
|
||||
|
||||
### Mistake 1: Non-Atomic Check-Then-Act
|
||||
|
||||
```java
|
||||
❌ WRONG: Race condition
|
||||
public void offer(DiagnosticData data) {
|
||||
if (buffer.size() < capacity) { // Check
|
||||
buffer.add(data); // Act (another thread may have added meanwhile)
|
||||
}
|
||||
}
|
||||
|
||||
✅ CORRECT: Atomic operation
|
||||
public void offer(DiagnosticData data) {
|
||||
buffer.offer(data); // ArrayBlockingQueue handles atomicity
|
||||
}
|
||||
```
|
||||
|
||||
### Mistake 2: Mutable Shared State
|
||||
|
||||
```java
|
||||
❌ WRONG: Mutable shared field
|
||||
public class Statistics {
|
||||
private long totalPolls = 0; // Not thread-safe!
|
||||
|
||||
public void increment() {
|
||||
totalPolls++; // Race condition: read-modify-write
|
||||
}
|
||||
}
|
||||
|
||||
✅ CORRECT: Atomic variable
|
||||
public class Statistics {
|
||||
private final AtomicLong totalPolls = new AtomicLong(0);
|
||||
|
||||
public void increment() {
|
||||
totalPolls.incrementAndGet(); // Atomic operation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mistake 3: Exposing Mutable Internal State
|
||||
|
||||
```java
|
||||
❌ WRONG: Exposing internal array
|
||||
public class DiagnosticData {
|
||||
private final byte[] data;
|
||||
|
||||
public byte[] getData() {
|
||||
return data; // Caller can modify internal state!
|
||||
}
|
||||
}
|
||||
|
||||
✅ CORRECT: Defensive copy
|
||||
public class DiagnosticData {
|
||||
private final byte[] data;
|
||||
|
||||
public byte[] getData() {
|
||||
return Arrays.copyOf(data, data.length); // Safe copy
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mistake 4: Synchronized on Long-Running Operation
|
||||
|
||||
```java
|
||||
❌ WRONG: Holding lock during I/O (blocks virtual threads)
|
||||
public synchronized byte[] pollEndpoint(String url) {
|
||||
return httpClient.send(request).body(); // I/O while holding lock!
|
||||
}
|
||||
|
||||
✅ CORRECT: No synchronization for thread-confined code
|
||||
public byte[] pollEndpoint(String url) {
|
||||
// Each thread has its own adapter instance
|
||||
return httpClient.send(request).body(); // No shared state
|
||||
}
|
||||
```
|
||||
|
||||
### Mistake 5: Inconsistent Locking
|
||||
|
||||
```java
|
||||
❌ WRONG: Inconsistent synchronization
|
||||
public void increment() {
|
||||
synchronized (this) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count; // Not synchronized! Can see stale value
|
||||
}
|
||||
|
||||
✅ CORRECT: Consistent synchronization or atomic variable
|
||||
private final AtomicInteger count = new AtomicInteger(0);
|
||||
|
||||
public void increment() {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count.get();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Thread Safety Review Checklist
|
||||
|
||||
Use this checklist during code reviews:
|
||||
|
||||
### Immutability
|
||||
- [ ] Value objects are `final` classes
|
||||
- [ ] All fields are `final`
|
||||
- [ ] No setter methods
|
||||
- [ ] Defensive copies for mutable fields (arrays, collections)
|
||||
- [ ] Getters return defensive copies of mutable fields
|
||||
|
||||
### Concurrent Collections
|
||||
- [ ] Use `BlockingQueue` for producer-consumer patterns
|
||||
- [ ] Use `ArrayBlockingQueue` for bounded buffers
|
||||
- [ ] Use `ConcurrentHashMap` for thread-safe maps
|
||||
- [ ] Avoid manual synchronization on collections
|
||||
|
||||
### Atomic Variables
|
||||
- [ ] Use `AtomicLong`/`AtomicInteger` for counters
|
||||
- [ ] Use atomic operations (`incrementAndGet`, `get`, `compareAndSet`)
|
||||
- [ ] No read-modify-write with plain `long`/`int`
|
||||
|
||||
### Locks
|
||||
- [ ] Use `ReentrantLock` instead of `synchronized` (for virtual threads)
|
||||
- [ ] Always unlock in `finally` block
|
||||
- [ ] No I/O operations while holding lock
|
||||
- [ ] Lock ordering documented (if multiple locks)
|
||||
|
||||
### Virtual Threads
|
||||
- [ ] Virtual threads used for I/O-bound tasks
|
||||
- [ ] No `synchronized` on long-running operations
|
||||
- [ ] No CPU-bound work in virtual threads
|
||||
|
||||
### Testing
|
||||
- [ ] Concurrent access tests exist (multiple threads)
|
||||
- [ ] Stress tests exist (100+ threads, 1000+ operations)
|
||||
- [ ] Race condition tests verify atomic operations
|
||||
- [ ] No flaky tests (deterministic results)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Java Concurrency References
|
||||
- "Java Concurrency in Practice" by Brian Goetz (Chapter 2-5)
|
||||
- JDK 25 Documentation: Virtual Threads (JEP 444)
|
||||
- Java Memory Model (JLS §17.4)
|
||||
|
||||
### Internal Documentation
|
||||
- [Project Implementation Plan](../PROJECT_IMPLEMENTATION_PLAN.md)
|
||||
- [Architecture Decisions](../ARCHITECTURE_DECISIONS.md)
|
||||
- [Code Review Guidelines](CODE_REVIEW_GUIDELINES.md)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Summary: Thread Safety Mindset
|
||||
|
||||
> **"Thread safety is not optional—it's correctness."**
|
||||
|
||||
### The Thread Safety Hierarchy (Prefer in Order)
|
||||
|
||||
1. **Immutability** (best): No shared mutable state
|
||||
2. **Thread Confinement**: State not shared between threads
|
||||
3. **Concurrent Collections**: Use built-in thread-safe collections
|
||||
4. **Atomic Variables**: For simple shared state (counters, flags)
|
||||
5. **Locks**: For complex synchronized operations (last resort)
|
||||
|
||||
### Key Principles
|
||||
|
||||
- **Design for immutability first**: Mutable shared state is the enemy
|
||||
- **Prefer composition over manual synchronization**: Use `BlockingQueue`, `AtomicLong`, etc.
|
||||
- **Test concurrency explicitly**: Don't rely on "it works in single-threaded tests"
|
||||
- **Document thread safety**: Javadoc must state thread safety guarantees
|
||||
|
||||
**When in doubt, ask: "What happens if two threads call this at the same time?"**
|
||||
|
||||
---
|
||||
|
||||
**Document Control**:
|
||||
- Version: 1.0
|
||||
- Created: 2025-11-20
|
||||
- Status: Active
|
||||
- Review Cycle: After each sprint
|
||||
Reference in New Issue
Block a user