Entities in business applications
Entities play an important role in the development of business applications for me. Before I start to develop, I first think about the business entities involved and their relationships to each other. An Entity-Relationship Model is a useful tool for describing and graphically representing the relationships between entities. A very typical relationship between entities is the 1:n relationship. In an e-commerce application, for example, a customer has no, one or any number of orders and an order is usually assigned to exactly one customer. Another example would be the relationship between an invoice and the invoice items it contains: An invoice consists of one or more invoice items, and an invoice item is assigned to exactly one invoice.
A suitable technology stack
When developing business applications, it is therefore important that the frameworks used support this typical relationship as conveniently and flexibly as possible. When developing business applications with Hilla, these requirements are met very well.
Spring Boot is used in the backend. In conjunction with relational databases, you can use Spring Data JPA and benefit from many advantages, including the following:
- The database schema can be generated automatically from the classes of the entities.
- Communication with the database can take place via the very powerful JPA repositories.
In the frontend, the application can be developed very quickly using the ready-made UI components of Hilla. For example, a typical master-detail UI pattern can be quickly implemented using the AutoGrid and AutoForm components.
Special characteristics of the 1:n relationship
Hilla already supports the 1:1 relationships of entities in the AutoGrid
component out-of-the-box (see Example). For 1:n relationships, you can easily customize both the AutoGrid
and AutoForm
components to support the relationship in a meaningful way.
Example
In the following example, this is shown using the entities Order
and Customer
. A customer has no, one or any number of orders and an order is assigned to exactly one customer. In the example, we first look at the 1:n relationship from the perspective of the order.
Backend
In the backend, the two entities Order
and Customer
are created first.
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
Long id;
@NotNull
private String number;
@NotNull
private LocalDate created;
@NotNull
@ManyToOne(targetEntity = Customer.class, optional = false)
private Customer customer;
// Getter and Setter omitted
}
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
Long id;
@NotBlank
String name;
@NotBlank
@Email
String mail;
// Getter and Setter omitted
}
Afterwards, the corresponding JPA repositories are created.
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {
List<Order> findAllByCustomerId(Long customerId);
}
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> { }
Communication between the backend and frontend takes place via corresponding services for Order
and Customer
, which are annotated with @BrowserCallable
. This is sufficient for Hilla to generate the required TypeScript classes, which can then be used in the frontend.
@BrowserCallable
@AnonymousAllowed
public class OrderService extends CrudRepositoryService<Order, Long, OrderRepository> {
public List<Order> getOrdersByCustomer(Long customerId) {
return super.getRepository().findAllByCustomerId(customerId);
}
}
@BrowserCallable
@AnonymousAllowed
public class CustomerService extends CrudRepositoryService<Customer, Long, CustomerRepository> { }
Frontend
The master view of the sample application shows all orders in the AutoGrid
component. A column for the customer is provided in the table. With the help of a custom render, you can flexibly determine which customer information should be displayed. In the example, the name of the customer is to be displayed.
<AutoGrid
model={OrderModel}
service={OrderService}
columnOptions={{
customer: {
renderer: ({ item }: { item: Order }) => <span>{item.customer?.name}</span>,
},
}}
/>
The AutoGrid
component has a great filter mechanism for each column. The column for the customer can also be given an individual filter.
<AutoGrid
model={OrderModel}
service={OrderService}
columnOptions={{
customer: {
renderer: ({ item }: { item: Order }) => <span>{item.customer?.name}</span>,
headerFilterRenderer: ({ setFilter }) => (
<TextField
placeholder='Filter...'
onValueChanged={({ detail }) =>
setFilter({
propertyId: 'customer.name',
filterValue: detail.value,
matcher: Matcher.CONTAINS,
'@type': 'propertyString',
})
}
/>
),
},
}}
/>
The detail view of the application shows an order in an AutoForm
component so that an order can be created and edited. The field for the customer can also be configured individually here. In this example, it makes sense to display the customer via a Combo Box component. This component enables the selection of a customer, including a helpful filter option.
<AutoForm
model={OrderModel}
service={OrderService}
item={order}
fieldOptions={{
customer: {
renderer: ({ field }) => <ComboBox {...field} items={customers} itemLabelPath='name' />,
},
}}
/>
Until now, the 1:n relationship between order and customer was viewed from the perspective of the order. If you want to turn the view around and, for example, view all of a customer’s orders, this can be implemented in the customer’s detail view, for example. The orders can be loaded using the customerId
via the individual getOrdersByCustomer
method of the OrderService
:
useEffect(() => {
if (customerId) {
OrderService.getOrdersByCustomer(Number.parseInt(customerId)).then(setOrders);
}
}, [customerId]);
If the orders of a customer are only to be displayed, the Grid component is suitable for this.
<Grid
items={orders}
>
<GridColumn path='number' />
<GridColumn path='created' />
</Grid>
Summary
The combination of Spring Boot and Spring Data JPA in the backend and the UI components and the code generation of Hilla in the frontend enable a very productive and at the same time very flexible development of business web apps that contain entities with common relationships such as 1:1 or 1:n.