Some checks failed
Build and publish Docker image / Build and push (push) Failing after 1m27s
Java 21 / Spring Boot 3.5.3 multi-module Maven project. Hybrid BM25+HNSW search with RRF, cross-encoder reranker, ONNX Runtime 1.22.0 (CPU + CUDA 12 GPU variants).
5.9 KiB
5.9 KiB
trueref — Code Style
1. Language & Toolchain
- Java 21, source/target 21.
- Maven with
spring-boot-maven-pluginfor the fat JAR. - Spotless with Palantir Java Format for formatting (4-space indent, 120 col).
- ErrorProne + NullAway for static analysis. NullAway annotations:
@org.jspecify.annotations.Nullable/@NonNull. - Hexagonal boundaries are enforced by Maven module dependencies (no ArchUnit). See ARCHITECTURE §3.
2. Records, Sealed Types, Pattern Matching
- Prefer records for DTOs, value objects, and
port.in/port.outparameter/result types. - Use sealed interfaces for closed result/event hierarchies (
sealed interface IngestionEvent permits ...). - Use pattern matching (
switchexpressions,instanceof) over visitor pattern.
3. Nullability & Optional
- All API surfaces (public methods on ports, REST DTOs) are non-null by default; mark nullable explicitly with
@Nullable. - Use
Optional<T>only as a return type from query-style methods. Never as a field, never as a parameter.
4. Concurrency
- Spawn virtual threads via
Thread.ofVirtual().start(...)orExecutors.newVirtualThreadPerTaskExecutor(). Never callThread.sleepinside a synchronized block. - Shared mutable state is forbidden in
domainand discouraged inapplication. When unavoidable, usejava.util.concurrent.atomic.*or aReentrantLock. - GPU work goes through
GpuSemaphore.acquire()(a thin wrapper aroundSemaphore). - Long-running orchestration uses structured concurrency (
StructuredTaskScope) where it improves cancellation safety.
5. Error Handling
- Domain errors are sealed exception hierarchies rooted at
TrueRefException. Adapters translate them to HTTP/JSON-RPC errors centrally (REST:@ControllerAdvice; MCP: dedicated translator). - No checked exceptions at port boundaries. Wrap third-party checked exceptions at the adapter edge.
- Validation errors carry a stable
code(string) so the UI can localize. - Never
catch (Exception e)and swallow. Either log + rethrow as a domain exception or let it propagate.
6. Logging
- SLF4J with parameterized messages:
log.info("indexed tag {} of repo {}", tag, repoName);— never string-concatenate. - Structured fields via MDC:
repoId,versionId,jobId,stage. Cleared in a try/finally. - Log levels:
ERROR: unrecoverable, requires operator attention.WARN: degraded, automatic recovery in progress.INFO: lifecycle events (job started/finished, repo registered).DEBUG: per-file, per-chunk detail. Off by default.
7. Naming
- Use cases (
port.in): imperative verb phrases —IndexVersion,ResolveLibraryId. - SPIs (
port.out): noun-ish role names —EmbeddingService,ChunkStore,GitClient. - Adapter classes:
<Tech><Role>—LuceneChunkStore,OnnxEmbeddingService,JGitClient. - DTOs:
<Resource><Action>Request/<Resource>Response. - Records' field names are camelCase (no Hungarian, no
_prefixes).
8. Package & File Discipline
- One public type per file.
- Internal helpers are package-private. Avoid
publicunless used across packages. - Domain packages export only records and interfaces. No Spring annotations, no Lombok, no Jackson annotations.
- Adapter packages may use Spring stereotypes (
@Component,@Repository,@RestController) but adapters depend on port interfaces only when interacting with the application.
9. Spring Wiring
- Wiring lives in
bootstrap. Each adapter package may define a@Configuration(constructor-injected@Beanfactories) but does not auto-@ComponentScanitself; bootstrap explicitly imports. - Use
@ConfigurationPropertiesrecords for typed config; never raw@Value. - Prefer constructor injection. No field injection.
10. Persistence (H2)
- Migrations under
src/main/resources/db/migrationnamedV<N>__<snake_case>.sql. Flyway runs at startup. - All access via Spring
JdbcClient(Spring Boot 3.2+, fluent JDBC). No JPA/Hibernate, noJdbcTemplatedirectly. - Mappers are explicit
RowMapper<T>lambdas, not reflection-based. - SQL lives next to the repository class, either as
static final Stringconstants or in*.sqlfiles loaded viaClassPathResourcefor non-trivial queries.
11. REST
- Controllers in
adapter.in.rest. They depend only onport.ininterfaces and DTO records. - DTOs are separate from domain records. Mapping via plain
static of(...)factories. No MapStruct. - All endpoints documented with
@Operation,@ApiResponses,@Schema(springdoc). - Request validation via
jakarta.validationannotations on DTOs. - SSE endpoints return
SseEmitter; subscribe toJobEventBus, unsubscribe on completion/timeout.
12. MCP
- Tool definitions are records decorated to produce JSON Schema via Spring AI's MCP support. Schema strings stay verbatim (1:1 with Context7) so LLMs see identical contracts.
- Tool handlers depend only on
port.in(SearchLibraryDocs,ResolveLibraryId).
13. Tests
- JUnit 5 + AssertJ + Mockito (sparingly).
- Unit tests live next to the package they test. Integration tests under
src/test/java/.../it/and use@SpringBootTest. - Use Testcontainers only when truly required (we mostly avoid it via embedded stores).
- ArchUnit test suite is mandatory and runs in CI.
14. Dependency Hygiene
- BOM-managed versions only. Add a dependency only if it provides clear value over JDK + Spring Boot + already-included libs.
- No Lombok, no Guava (use JDK 21 equivalents), no Reactor (we use virtual threads + blocking).
- No Kotlin, no Scala.
15. Documentation
- All architectural decisions go in ARCHITECTURE.md.
- All research notes go in FINDINGS.md with sources.
- All conventions go in this file.
- Per-package
package-info.javamay exist for non-trivial packages, summarizing role and exported types. - No README sprawl:
README.mdis a quickstart only and links to the three docs above.