CtrlK
BlogDocsLog inGet started
Tessl Logo

opik-backend

Java backend patterns for Opik. Use when working in apps/opik-backend, designing APIs, database operations, or services.

73

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Opik Backend

Architecture

  • Layered: Resource → Service → DAO (never skip layers)
  • DI: Guice modules, constructor injection with @Inject
  • Databases: MySQL (metadata, transactional) + ClickHouse (analytics, append-only)

Naming Conventions

Plural Names (Resources, Tests, URLs, DB Tables)

  • Resource classes: TracesResource, SpansResource, DatasetsResource (not TraceResource)
  • Resource test classes: TracesResourceTest, SpansResourceTest, DatasetsResourceTest (not TraceResourceTest)
  • URL paths: /v1/private/traces, /v1/private/spans (not /v1/private/trace)
  • DB table names: traces, spans, feedback_scores (not trace, span, feedback_score)

Singular Names (DAO, Service)

  • DAO classes: TraceDAO, SpanDAO, DatasetDAO (not TracesDAO)
  • Service classes: TraceService, SpanService, DatasetService (not TracesService)
// ✅ GOOD
@Path("/v1/private/traces")
public class TracesResource { }

// ✅ GOOD - DAO and Service use singular
public class TraceDAO { }
public class TraceService { }

// ✅ GOOD - test classes match plural resource name
public class TracesResourceTest { }

// ❌ BAD - singular test class
public class TraceResourceTest { }

// ❌ BAD - singular resource/URL
@Path("/v1/private/trace")
public class TraceResource { }

// ❌ BAD - plural DAO/Service
public class TracesDAO { }
public class TracesService { }

Lombok Conventions

Records and DTOs

  • Always annotate records/DTOs with @Builder(toBuilder = true)
  • Use builders (not constructors) when instantiating records
  • For internal records (built programmatically, never validated by Bean Validation), use Lombok @NonNull on required fields — it generates a runtime null check at construction
  • For request-body DTOs validated via @Valid cascade (Jakarta validators like @NotNull/@NotBlank/@Size), use Jakarta annotations only — do not stack @NonNull on top. Bean Validation already enforces the contract at the API boundary; doubling up is redundant noise
// ✅ GOOD - internal record, Lombok @NonNull
@Builder(toBuilder = true)
record MyData(@NonNull UUID id, @NonNull String name, String description) {}

MyData data = MyData.builder()
        .id(id)
        .name(name)
        .build();

// ✅ GOOD - request-body DTO, Jakarta validators only
@Builder(toBuilder = true)
public record MyRequest(
        @NotNull UUID id,
        @NotBlank String name,
        @NotNull @Size(min = 1, max = 1000) @Valid List<MyItem> items) {}

// ❌ BAD - plain constructor (positional mistakes, less readable)
new MyData(id, name, null);

// ❌ BAD - @Builder without toBuilder
@Builder
record MyData(UUID id, String name) {}

// ❌ BAD - stacking @NonNull and @NotNull on the same field
public record MyRequest(@NonNull @NotNull UUID id) {}

Dependency Injection

  • Use @RequiredArgsConstructor(onConstructor_ = @Inject) instead of manual constructors
// ✅ GOOD
@RequiredArgsConstructor(onConstructor_ = @Inject)
public class MyService {
    private final @NonNull DependencyA depA;
    private final @NonNull DependencyB depB;
}

// ❌ BAD - boilerplate constructor
public class MyService {
    private final DependencyA depA;
    @Inject
    public MyService(DependencyA depA) {
        this.depA = depA;
    }
}

Interfaces

  • Don't put validation annotations (@NonNull) on interface method parameters
  • Keep interfaces free of implementation details
// ✅ GOOD
interface MyService {
    void process(String workspaceId, UUID promptId);
}

// ❌ BAD - validation on interface
interface MyService {
    void process(@NonNull String workspaceId, @NonNull UUID promptId);
}

Critical Gotchas

StringTemplate Memory Leak

// ✅ GOOD
var template = TemplateUtils.newST(QUERY);

// ❌ BAD - causes memory leak via STGroup singleton
var template = new ST(QUERY);

List Access

// ✅ GOOD
users.getFirst()
users.getLast()

// ❌ BAD
users.get(0)
users.get(users.size() - 1)

SQL Text Blocks

// ✅ GOOD - text blocks for multi-line SQL
@SqlQuery("""
        SELECT * FROM datasets
        WHERE workspace_id = :workspace_id
        <if(name)> AND name like concat('%', :name, '%') <endif>
        """)

// ❌ BAD - string concatenation
@SqlQuery("SELECT * FROM datasets " +
        "WHERE workspace_id = :workspace_id " +
        "<if(name)> AND name like concat('%', :name, '%') <endif> ")

Immutable Collections

// ✅ GOOD
Set.of("A", "B", "C")
List.of(1, 2, 3)
Map.of("key", "value")

// ❌ BAD
Arrays.asList("A", "B", "C")

API Design

  • Query parameters that accept lists: Use plural names from the start (e.g., exclude_category_names not exclude_category_name). Starting with a singular name and later adding a plural variant results in two redundant query params on the same endpoint. Plural names are backward-compatible since they work for both single and multiple values.

Error Handling

Use Jakarta Exceptions

throw new BadRequestException("Invalid input");
throw new NotFoundException("User not found: '%s'".formatted(id));
throw new ConflictException("Already exists");
throw new InternalServerErrorException("System error", cause);

Error Response Classes

  • Simple: io.dropwizard.jersey.errors.ErrorMessage
  • Complex: com.comet.opik.api.error.ErrorMessage
  • Never create new error message classes

Logging

Format Convention

// ✅ GOOD - values in single quotes
log.info("Created user: '{}'", userId);
log.error("Failed for workspace: '{}'", workspaceId, exception);

// ❌ BAD - no quotes
log.info("Created user: {}", userId);

Never Log

  • Emails, passwords, tokens, API keys
  • PII, personal identifiers
  • Database credentials

Reference Files

  • clickhouse.md - ClickHouse query patterns
  • mysql.md - TransactionTemplate patterns
  • testing.md - PODAM, naming, assertion patterns
  • migrations.md - Liquibase format for MySQL/ClickHouse
  • permissions.md - @RequiredPermissions annotation guidance for endpoints
Repository
comet-ml/opik
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.