Skip to content

Contributing and Conventions

Gabor Galazzo edited this page Jul 25, 2026 · 1 revision

Contributing and Conventions

The authoritative process document is CONTRIBUTING.md. This page summarises it and adds the code conventions that follow from the architecture.

Participation is governed by the Code of Conduct.


1. Branching

Branch Purpose
main Stable, protected. Direct pushes blocked
feat/<short-description> New features
fix/<short-description> Bug fixes
chore/<short-description> Tooling, dependencies, refactors
docs/<short-description> Documentation only

Branch from main; rebase on main before opening a PR.


2. Commits

Conventional Commits:

<type>(<scope>): <short summary in present tense>

<body — explain WHY, not what. Include context, trade-offs, constraints.>

Types: feat, fix, chore, docs, test, refactor, perf

Scopes (the module name): transport-api, transport-netty, server, bundle, common, consumer-state-store, lab, build, ci

feat(server): add TokenValidator SPI for per-handshake auth

fix(bundle): DEGRADED channel drops in-flight responses

chore(build): bump Spring Boot to 3.5.5

The body carries the weight. git show <hash> should be self-contained enough that someone can understand the motivation without external context. This is not ceremony — several of the subtlest behaviours in this codebase (the supersede skip, canSend() on DEGRADED, growth-first queueing) are only comprehensible from the commit that explains why.


3. Pull requests

  1. All tests pass locally
  2. PR template filled in completely — summary plus test plan
  3. Related issues linked
  4. One maintainer approval required
  5. Merged with squash-and-merge (linear main history)
  6. CHANGELOG.md updated under [Unreleased]

The PR title follows the same Conventional Commits format as a commit message.

Call out security-sensitive changes explicitly in the description so maintainers can prioritise review: authentication, TLS, token validation, wire protocol, serialization, event-store persistence, and dependency updates with known CVEs.


4. Coding standards

  • Java 25 — records, sealed interfaces, pattern matching, virtual threads where appropriate. No preview features in production code.
  • No System.exit, anywhere. Failures surface through futures, exceptions or callbacks.
  • Constructors do no work. Thread starts and I/O belong in an explicit start().
  • OCP via dispatcher maps — Map<Class<? extends Message>, Handler>, not switch chains. Adding a handler type must not touch existing dispatch code.
  • Records for all value types — Message subtypes, BundleRegistrationInfo, ConsumerCheckpoint subtypes, ConsumerErrorState, VersionedCheckpoint, and so on.
  • Tests at the boundary. Real TCP, not mocks.
  • No comments that restate the code. Comment non-obvious invariants, constraints and workarounds only.
  • No extra abstractions. Solve the problem at hand; do not design for hypothetical futures.
  • Virtual threads for business executors. Never block a Netty EventLoop — CBOR decode and handler dispatch both hand off.

Style is enforced by .editorconfig: 4-space indent for Java, LF line endings, 120-character soft limit.


5. Conventions with teeth

Three rules are load-bearing rather than stylistic. Breaking them causes real, subtle bugs.

Adding a wire message type

Two steps, and the compiler enforces the rest:

  1. Extend the permits clause of the sealed Message interface
  2. Register a byte tag in MessageTypeRegistry

Every non-exhaustive switch in the codebase then fails to compile until it handles the new type. Do not work around this with a default branch.

Evolving a wire DTO

Wire DTOs evolve by addition, and every codec tolerates unknown properties. When adding a field:

  • keep FAIL_ON_UNKNOWN_PROPERTIES disabled in all four mappers (JacksonCborCodec, JacksonCborPayloadCodec, AdminPayloadCodec, ObjectMapperUtils), and
  • give the new record component a null-normalising @JsonCreator.

CodecVersionToleranceTest pins both skew directions. See Wire Protocol § 8 for the near-invisible outage that strictness caused — and note that a derived getter on a wire DTO is enough to trigger it.

Preserving zero-copy forwarding

Every Netty-to-Netty forward must take the raw path (Transport.sendRaw). The counters forwardedRawCount / forwardedReencodedCount are exported as evento.server.forwarded{path=raw|reencoded} and tests assert the contract.


6. The public API surface

Changing any of these signatures requires a major version bump:

  • Annotations — @Aggregate, @CommandHandler, @AggregateCommandHandler, @EventHandler, @EventSourcingHandler, @QueryHandler, @SagaEventHandler, @InvocationHandler, @Service, @Projector, @Projection, @Saga, @Observer, @Invoker
  • Bundle bootstrap — EventoBundle.Builder
  • Modeling types — AggregateState, Event, Command, Query, DomainCommandMessage, DomainEventMessage, ServiceCommandMessage, ServiceEventMessage, DecoratedDomainCommandMessage
  • Gateways — CommandGateway, QueryGateway
  • Consumer SPIs — ConsumerStateStore, ConsumerLock, SagaStateStore, DeadEventQueue, DedupeStore, ConsumerEngineConfig, ConsumerExecutor + ConsumerExecutors
  • Server Spring starter — the evento.server.bus.* properties and BusFacade autowiring

7. Licensing

By submitting a pull request you agree that your contribution is licensed under the project's dual licence — AGPL-3.0 for open source, commercial licence for proprietary use.


8. Project governance

Roles, decision-making, release responsibilities and ownership are documented in GOVERNANCE.md and MAINTAINERS.md.


See also

Clone this wiki locally