Skip to content

Naming Rules — Derived Class Names as a Replaceable Service

Overview and Motivation

A single PDO or operation interface such as Invoice fans out into a whole family of types: the domain interface and implementation, the persistence interface and implementation, and — when remoting is enabled — a remote delegate interface and implementation. Tentackle never hard-codes how these names are formed. All of them are derived by NamingRules, a singleton in tentackle-common that acts as the single authority for class-naming conventions — consulted at build time by the code generators and the wizard, and at runtime to locate the remote delegates.

Because the singleton is created via the service API, an application can replace it with @Service(NamingRules.class) and establish its own conventions — without touching the framework, the wurblets, or the wizard templates.

The Default Conventions

Artifact Default name Example for Invoice
Domain interface <Pdo>Domain InvoiceDomain
Domain implementation <Pdo>DomainImpl InvoiceDomainImpl
Persistence interface <Pdo>Persistence InvoicePersistence
Persistence implementation <Pdo>PersistenceImpl InvoicePersistenceImpl
Remote delegate interface <Pdo>RemoteDelegate InvoiceRemoteDelegate
Remote delegate implementation <Pdo>RemoteDelegateImpl InvoiceRemoteDelegateImpl
Remote delegate package persistence-impl package + .trip com.example.persist.trip

Two things are worth noting:

  • Operations follow the same rules as PDOs by default: the getOperation… methods simply delegate to their getPdo… counterparts. They are separate methods, though, so a replacement may give operations different conventions.
  • Apart from the .trip sub-package for remote delegates, packages are not a NamingRules concern — where the domain and persistence types live is decided by the application layout (and, when scaffolding, by the wizard profiles). NamingRules derives simple class names, plus the remote sub-package relative to the persistence implementation's package.

The API: Forward and Reverse

Every rule comes as a pair: a forward method deriving the name, and a reverse method parsing it back — returning null if the given name does not match the convention:

NamingRules rules = NamingRules.getInstance();

rules.getPdoPersistenceImplementation("Invoice");          // "InvoicePersistenceImpl"
rules.getPdoFromPersistenceImplementation("InvoicePersistenceImpl");  // "Invoice"
rules.getPdoFromPersistenceImplementation("SomethingElse");           // null

The reverse methods matter because the framework not only generates names but also recognizes them — for example, when tooling maps an implementation class back to its PDO. A replacement must therefore always override forward and reverse methods consistently.

Who Uses the Rules

At runtime: locating the remote delegates

In a multi-tier setup, every persistence class talks to the tier above through a per-session remote delegate (see TRIP). These delegate classes are found by name, not by registration: the RemoteDbDelegateLocator (itself a replaceable @Service) takes the package of the persistence implementation and the simple name of its effective class, asks NamingRules for the remote interface and implementation names in the .trip sub-package, and loads them via Class.forName — verifying that the results really are a RemoteDbDelegate interface and a RemoteDbDelegateImpl class.

This is why generated delegates need no META-INF entries at all: the convention is the registration. Handwritten delegates that live elsewhere can opt out by annotating the implementation with @RemoteService, which registers it as a mapped service and bypasses the naming lookup (see RemoteDbSession).

At build time: the code generators

The persistence wurblets consult the same singleton: the generated getRemoteDelegate() in a persistence implementation is typed with the derived remote-interface name, @RemoteMethod-based remoting uses it to address the delegate, and AssertRemote creates missing remote interface/implementation source files from FreeMarker templates — in the package and under the names NamingRules dictates.

At scaffolding time: the wizard

The wizard derives every generated source name from the entity or operation name via NamingRules — the domain and persistence interfaces and implementations, the super-type names for inherited entities, and the remote packages passed to the templates.

Replacing the Rules

Applications with their own naming conventions subclass NamingRules, override the relevant method pairs, and register the subclass:

@Service(NamingRules.class)
public class MyNamingRules extends NamingRules {

  @Override
  public String getPdoPersistenceImplementation(String pdoName) {
    return pdoName + "Db";
  }

  @Override
  public String getPdoFromPersistenceImplementation(String persistenceImplementation) {
    return persistenceImplementation.endsWith("Db")
           ? persistenceImplementation.substring(0, persistenceImplementation.length() - 2)
           : null;
  }
}

Service resolution follows the module order described in Ordering and Overriding, so the application's registration wins over the default.

Because the rules are consulted in two worlds, a replacement must be visible in both:

  • at runtime, as an ordinary dependency of the application — otherwise the server derives the wrong delegate names and remote session setup fails with a ClassNotFoundException;
  • at build time, on the classpath of the build tooling that generates code — i.e. as a plugin dependency of the wurbelizer-maven-plugin (which runs the wurblets) and of the tentackle-wizard-maven-plugin.

The build-time and runtime rules must agree: the names woven into the generated sources are the names the runtime locator will look for.