Blog

Using microservices from Hilla

By  
Marcus Hellberg
Marcus Hellberg
·
On Aug 16, 2023 12:00:00 AM
·

Hilla is a full-stack framework for building web applications with Spring Boot and React. It embraces the backends for frontends (BFF) pattern, where backend services are tailored for specific frontends.

Hilla makes it easy to connect Java backends to TypeScript React frontends using type-safe RPC endpoints. You can call Java methods directly from TypeScript without manually defining REST APIs.

In this post, we'll look at consuming microservices with Hilla through a simple e-commerce example application.

Project Overview

Our application has two backend services - a user service and an order service. A Hilla application combines data from the services and exposes it to the React frontend.

The Hilla application combines data from the user and order services
The Hilla application combines data from the user and order services

The Hilla app acts as an aggregator and provides a single endpoint for the frontend. This avoids the frontend having to call multiple services directly.

The Backend Services

The user and order services are regular Spring Boot apps. They use JPA and Spring Data to persist entities to a database.

Here are the relevant parts of the services:

User Service

@Entity
public class User {

  @Id
  private Long id;

  private String name;

  private String email;

  // getters and setters
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

@Service
public class UserService {

  private final UserRepository userRepository;

  // ...

  public List<User> getAllUsers() {
    return userRepository.findAll();
  }

}

@RestController
@RequestMapping("/users")
public class UserController {

  private final UserService userService;

  // ...

  @GetMapping
  public List<User> getAllUsers() {
    return userService.getAllUsers();
  }

}

Order Service

@Entity
public class Order {

  @Id
  private Long id;

  private Long userId;

  private String product;

  private Double price;

  // ...
}

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

  List<Order> findByUserId(Long userId);

}

@Service
public class OrderService {

  private final OrderRepository orderRepository;

  // ...

  public List<Order> getOrdersForUser(Long userId) {
    return orderRepository.findByUserId(userId);
  }

}

@RestController
@RequestMapping("/orders")
public class OrderController {

  private final OrderService orderService;

  // ...

  @GetMapping("/user/{userId}")
  public List<Order> getOrdersForUser(@PathVariable Long userId) {
    return orderService.getOrdersForUser(userId);
  }

}

These are standard Spring Boot REST controllers that return JSON responses.

The Hilla Application

The Hilla app calls the user and order services and combines the data into a single type-safe endpoint for the frontend:

@Endpoint
public class UserDetailsService {

  public record User(Long id, String name, String email) {}

  public record Order(Long id, Long userId, String product, Double price) {}

  public record UserDetails(User user, List<Order> orders) {}

  public List<UserDetail> getUserDetails() {

    // Call user service to get all users
    var users = getUsers();
    return users.stream()
            .map(user -> {
                // Call order service to get all orders for each user
                var orders = getOrders(user.id());
                return new UserDetail(user, orders);
            })
            .toList();
  }

  private List<User> getUsers() {
      WebClient userClient = webClientBuilder.baseUrl(userServiceUrl).build();
      var users = userClient.get()
              .uri("/users")
              .retrieve()
              .bodyToFlux(User.class)
              .collectList()
              .block();

      return users;
  }

  private List<Order> getOrders(Long userId) {
      WebClient orderClient = webClientBuilder.baseUrl(orderServiceUrl).build();
      var orders = orderClient.get()
              .uri("/orders/user/" + userId)
              .retrieve()
              .bodyToFlux(Order.class)
              .collectList()
              .block();

      return orders;
  }

}

The @Endpoint annotation exposes getUserDetails as a public endpoint that's callable from TypeScript.

The frontend can now fetch the aggregated user details in one call from TypeScript:

const userDetails = await UserDetailsService.getUserDetails();

No need to manually call the /users and /orders endpoints separately.

The Frontend

Putting it all together, the frontend can fetch user details and display them in a grid based on the selected user:

export default function App() {
  const [userDetails, setUserDetails] = useState<UserDetail[]>([]);
  const [orders, setOrders] = useState<Order[]>([]);

  useEffect(() => {
    UserDetailsService.getUserDetails().then(setUserDetails);
  }, []);

  function selectedUserChanged(
    e: ComboBoxSelectedItemChangedEvent<UserDetail>
  ) {
    const orders = e.detail.value ? e.detail.value.orders : [];
    setOrders(orders);
  }

  return (
    <div className="flex flex-col items-start gap-l p-m">
      <h1>Hilla microservice example</h1>

      <ComboBox
        label="Select user to view orders"
        items={userDetails}
        itemLabelPath="user.name"
        onSelectedItemChanged={selectedUserChanged}
      />

      <Grid items={orders}>
        <GridColumn path="product" />
        <GridColumn path="price" />
      </Grid>
    </div>
  );
}

Conclusion

Hilla makes it easy to integrate Java microservices with a React frontend using type-safe endpoints. The Hilla app can aggregate data from services and act as a backend for the UI.

This example showed a simple read-only application, but Hilla endpoints also support taking complex parameters and returning domain objects. That makes it easy to build full CRUD functionality across microservices.

Check out the Hilla microservices example to see the full application code. Let us know if you have any other questions!

Marcus Hellberg
Marcus Hellberg
Marcus is the VP of Developer Relations at Vaadin. His daily work includes everything from writing blogs and tech demos to attending events and giving presentations on all things Vaadin and web-related. You can reach out to him on Twitter @marcushellberg.
Other posts by Marcus Hellberg