The decorator / annotation model
Dirigible's modern development model is decorator-driven on TypeScript and annotation-driven on Java, with the two surfaces deliberately kept symmetric. The same concept (@Controller, @Entity, @Inject, @Scheduled, @Listener) means the same thing on both sides; only the import root and the syntax differ.
Parallel surface
| Concept | TypeScript | Java |
|---|---|---|
| REST controller | @Controller("/users") from @aerokit/sdk/http/decorators | @Controller("/users") from org.eclipse.dirigible.sdk.http |
| HTTP method | @Get("/{id}") | @Get("/{id}") |
| Path / query / body | @PathParam, @QueryParam, @Body | @PathParam, @QueryParam, @Body |
| Entity | @Entity("User") from @aerokit/sdk/db/decorators | @Entity from org.eclipse.dirigible.sdk.db |
| Primary key | @Id + @Generated("sequence") | @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) |
| Column | @Column({name, type}) | @Column(name=..., length=..., nullable=...) |
| Audit fields | @CreatedAt, @UpdatedAt, @CreatedBy, @UpdatedBy | @CreatedAt, @UpdatedAt, @CreatedBy, @UpdatedBy |
| Repository / DI | @Component("CountryRepository") | @Repository |
| Inject | @Inject("CountryRepository") | @Inject |
| Scheduled job | @Scheduled(...) | @Scheduled(expression="0 0 * * * ?") + optional implements JobHandler |
| Message listener | @Listener(...) | @Listener(name="queue.x", kind=ListenerKind.QUEUE) + optional implements MessageHandler |
| Websocket | @Websocket(...) | @Websocket(name="chat", endpoint="chat") + optional implements WebsocketHandler |
| Extension point | @ExtensionPoint("description") on an interface | @ExtensionPoint("description") on an interface |
| Extension provider | @Extension({target: Contract}) | @Extension(target=Contract.class, name="...") |
| Role check | @Roles(["admin"]) | @Roles({"admin"}) |
How the wiring happens
The mechanism behind each declaration is the same: at class-load time a consumer reflects over the annotations and registers the class with the relevant platform service.
On the Java side this is the JavaClassConsumer SPI run in fixed @Order:
EntityClassConsumer(@Order(100)) -@Entityclasses register withJavaEntityManager.RepositoryClassConsumer(@Order(200)) -@Repositoryclasses are instantiated and stored inRepositoryRegistry.ControllerClassConsumer(@Order(300)) -@Controllerclasses have their@Injectfields resolved, then their routes registered withControllerRouter.HandlerClassConsumer- claimsimplements JavaHandler.
The ordering guarantees that @Inject CountryRepository resolves inside the controller consumer - the repository has already been registered in the same rebuild cycle.
Optional typed contracts
@Scheduled, @Listener, and @Websocket each ship a companion interface in the SDK (JobHandler, MessageHandler, WebsocketHandler). Implementing the interface is opt-in - the runtime falls back to method-by-name reflection when the interface is absent, so existing handlers keep working unchanged. The typed path gives compile-time signature checking, IDE autocomplete and refactoring, default no-op methods for callbacks you don't override (WebsocketHandler, MessageHandler.onError), and direct (non-reflective) dispatch. See the per-decorator pages under /sdk/ for details.
On the TypeScript side, dedicated synchronizers (ComponentSynchronizer, EntitySynchronizer, OpenAPISynchronizer, plus the listener / job / websocket / extension synchronizers) scan files matching *Component.ts, *Entity.ts, *Controller.ts, etc. and do the same registration work.
Pages by concept
- REST APIs -
@Controller, the method verbs, parameter binding, return-value writing. - Entities and persistence -
@Entity,@Id,@Column, audit fields, repository pattern. - Dependency injection -
@Inject,@Repository,@Component. - Scheduled jobs -
@Scheduled. - Message listeners -
@Listener. - Extension providers -
@Extension. - Websockets -
@Websocket. - Security and roles -
@Roles.
See also
- The synchronizer model - how files become runtime state.
- Java SDK reference.
- TypeScript API reference.