Skip to content

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

ConceptTypeScriptJava
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:

  1. EntityClassConsumer (@Order(100)) - @Entity classes register with JavaEntityManager.
  2. RepositoryClassConsumer (@Order(200)) - @Repository classes are instantiated and stored in RepositoryRegistry.
  3. ControllerClassConsumer (@Order(300)) - @Controller classes have their @Inject fields resolved, then their routes registered with ControllerRouter.
  4. HandlerClassConsumer - claims implements 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

See also

Released under the EPL-2.0 License.