Skip to content

Mateu and system architecture

The UI is an inbound adapter. This is the central architectural idea in Mateu, and it has practical consequences for how you structure your application.

In hexagonal architecture, inbound adapters are the entry points into your application:

Infrastructure / inbound
-> REST controllers
-> gRPC endpoints
-> message consumers
-> scheduled jobs
-> UI <-- Mateu belongs here

Each adapter translates an external signal into a call to the application core. A REST controller translates an HTTP request. Mateu translates a browser interaction.

This means a Mateu ViewModel can call:

  • application use cases directly
  • query services
  • ports and gateways
  • domain services

without any additional API layer in between. The UI is a first-class consumer of the application core, not a downstream client of a REST API.

Traditional internal tools are built with an API in the middle:

Backend -> REST API -> API client -> Frontend model -> Browser

That API exists primarily because the frontend is a separate application. When the UI is an inbound adapter, the API between frontend and backend becomes unnecessary for the internal UI. The ViewModel calls the application core directly.

Backend -> Mateu ViewModel (inbound adapter) -> Browser

The REST API still exists for external consumers. It is not replaced. It is just no longer the only way for the UI to talk to the backend.

Mateu works well with DDD because ViewModels map naturally to domain concepts.

A ProductEditor ViewModel calls SaveProductUseCase. It does not call a generic /api/products endpoint that was designed to serve any consumer. It calls the specific use case that represents the intent.

@Override
public void save(HttpRequest request) {
saveProductUseCase.handle(
new SaveProductCommand(id, name, price, status)
);
}

This keeps the ViewModel aligned with the domain, not with a REST contract.

For listings, Mateu connects naturally to query services.

@Override
public ListingData<ProductRow> search(String searchText, ProductFilters filters, Pageable pageable) {
return productQueryService.findAll(searchText, filters, pageable);
}

The list view calls the query side. The edit form calls the command side. The split is clean and explicit.

Validation belongs in the domain — in value objects, aggregates, and use cases. Mateu enforces Bean Validation constraints in the browser as a user experience feature, but the authoritative enforcement happens in the application core. The ViewModel does not duplicate validation logic; it delegates to it.

In a distributed system, each service can own its own Mateu UI. Services expose their UI through @UI roots. A shell application composes them using RemoteMenu.

@UI("/admin")
public class ShellRoot {
@Menu
RemoteMenu orders = new RemoteMenu("/_orders-service");
@Menu
RemoteMenu products = new RemoteMenu("/_products-service");
}

The shell does not know the internal structure of each service’s UI. It only knows the remote URL. The service controls what it exposes.

ConcernOwner
Domain logicdomain layer (aggregates, value objects)
Use casesapplication layer
Data accessrepositories and query services
UI definitionMateu ViewModel (inbound adapter)
RenderingMateu renderer