DEV Community

Poja
Poja

Posted on

The Convention Is the Wiring

You already trust conventions to wire your Spring Boot application. What happens when the same idea reaches the infrastructure?

Part 1: Convention Over Configuration, Spring Boot Style

In Spring Boot, where you place your main class determines what the application discovers, because @SpringBootApplication scans that package and everything below it.

The same principle runs deeper. Auto-configuration derives parts of the application from what's on the classpath, while configuration itself follows conventions such as application.yaml.

Rails made the idea famous as convention over configuration. Put simply: if something can be derived, why declare it?

The convention is doing part of the wiring for you.

But that idea usually stops at the application boundary. Your application knows its own beans. AWS, on the other hand, still needs the queue, retries, routing, permissions, mappings, and the rest of the infrastructure around them.

Part 2: What "Process This Asynchronously" Actually Costs

Take an ordinary requirement: "I want to process this asynchronously." It sounds like one sentence. It isn't.

What the requirement asks for What the infrastructure has to provide
An event that arrives reliably A queue, plus a dead-letter queue for failures, with maxReceiveCount deciding when a failed message is moved
Retries that don't fire too early or too often A visibility timeout of at least six times the function timeout, and a maxReceiveCount of at least 5, per AWS's own guidance
An event that reaches the right handler An event bus and rules, where the pattern either matches or it doesn't: one mismatch in the source field and the event goes nowhere
Permission to actually run Roles, policies, event source mappings, and the configuration connecting everything together

None of that changes what your application is trying to do. It's just the infrastructure required to make that intent run. And that creates an awkward split: the developer describes the event in Java, while the infrastructure describes how that event moves through AWS.

So the real question isn't "can this be built." It's: how much of that wiring should the developer have to declare manually?

Part 3: Letting the Convention Do the Wiring

With Poja, you don't configure the async stack yourself. You follow a few conventions in your code, and Poja takes care of the infrastructure.

For asynchronous processing, you only need to:

  • Extend PojaEvent and declare how long your handler may take
  • Name the consumer after the event
  • Put both classes in the expected packages

For example:

package your.package.endpoint.event.model;

public class SendEmailRequested extends PojaEvent {
    // event data

    @Override
    public Duration maxConsumerDuration() {
        return Duration.ofSeconds(45);
    }

    @Override
    public Duration maxConsumerBackoffBetweenRetries() {
        return Duration.ofSeconds(30);
    }
}
Enter fullscreen mode Exit fullscreen mode

and:

package your.package.service.event;

@Service
public class SendEmailRequestedService
        implements Consumer<SendEmailRequested> {

    @Override
    public void accept(SendEmailRequested event) {
        // handle event
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it. The convention is documented, and the dispatcher builds the service class name by concatenation. The link between your event and the code that consumes it is a string addition: Poja takes the event's fully-qualified name, keeps the last segment, and appends Service to resolve the consumer. Poja then uses those two durations to calculate the SQS visibility timeout for your message.

The rest of the stack works by injection rather than by naming:

  • Need to send an email? Inject Mailer.
  • Need S3? Inject BucketComponent, once file storage is enabled in your configuration.
  • Need to trigger another event? Inject EventProducer.

No AWS SDK to wire. No infrastructure configuration to maintain for the stack Poja provides.

Part 4: The Discipline the Convention Demands

Conventions make the infrastructure wiring simpler, and they have to be followed exactly. The event and its consumer are matched by name, so a naming mistake doesn't show up at compile time, it shows up when the event is consumed.

The asynchronous path is derived from those conventions. Other parts of the stack are reached by injection, as shown above, while resources outside the infrastructure Poja provisions still require their own configuration.

Within that scope, the name you write is what Poja reads to derive the queue, routing, and visibility settings around your handler.

You write the convention. Poja wires the infrastructure.

Try It On Your Own Code

Two classes and a git push are the whole experiment. Deploy one event, then look at what came out of it: the queue, the routing rule, and the visibility timeout Poja calculated from your two durations.

Wire your first event →

Top comments (0)