🧩🚀 MICROSERVICES WITH JAKARTA EE: CLIENT + CONTROLLER/SERVICE/DAO (WITH CODE)
🧩🚀 MICROSERVICES WITH JAKARTA EE: CLIENT + CONTROLLER/SERVICE/DAO (WITH CODE)
🔸 TLDR 🧠
▪️ Use Jakarta EE standards to build microservices cleanly: Client (JAX-RS Client) → Controller (JAX-RS) → Service (@Transactional) → DAO (JPA).

🔸 THE IDEA
▪️ Build one microservice as a “frontend/BFF” that calls other services (still Java, still Jakarta EE).
▪️ Build one backend microservice with clean layers: Controller (JAX-RS) → Service → DAO (JPA).
▪️ Benefit: clear boundaries, testable code, and standard APIs (jakarta.*) ✨
🔸 FRONTEND: MICROSERVICE CLIENT CODE (CALLING THE BACKEND) 🌐
Example: a “frontend” microservice calls customer-service via the Jakarta REST (JAX-RS) Client API.
package com.example.frontend.client; import jakarta.enterprise.context.ApplicationScoped; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.MediaType; import java.time.Duration; @ApplicationScoped public class CustomerApiClient { private final Client client; private final Jsonb jsonb = JsonbBuilder.create(); public CustomerApiClient() { this.client = ClientBuilder.newBuilder() // vendor-specific config often lives here (timeouts, TLS, etc.) .build(); } public CustomerDto getCustomer(long id) { WebTarget target = client .target("http://customer-service:8080") .path("/api/customers/{id}") .resolveTemplate("id", id); String json = target .request(MediaType.APPLICATION_JSON) .get(String.class); return jsonb.fromJson(json, CustomerDto.class); } // DTO kept simple for the “frontend/BFF” boundary public record CustomerDto(long id, String name, String email) {} }
✅ Why this approach
▪️ No magic: standard JAX-RS client
▪️ Easy to wrap with caching, retries, auth headers, correlation IDs 🔐📌
▪️ Keeps “frontend orchestration” out of the domain service
🔸 BACKEND: CONTROLLER + SERVICE + DAO (JAKARTA EE LAYERS) 🏗️
✅ 1) Controller (JAX-RS resource) — HTTP boundary
package com.example.customer.api; import com.example.customer.service.CustomerService; import jakarta.inject.Inject; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @Path("/api/customers") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class CustomerResource { @Inject CustomerService service; @GET @Path("/{id}") public CustomerDto get(@PathParam("id") long id) { return service.getById(id) .map(c -> new CustomerDto(c.getId(), c.getName(), c.getEmail())) .orElseThrow(() -> new NotFoundException("Customer " + id + " not found")); } @POST public Response create(@Valid CreateCustomerRequest req) { var created = service.create(req.name(), req.email()); return Response.status(Response.Status.CREATED) .entity(new CustomerDto(created.getId(), created.getName(), created.getEmail())) .build(); } public record CustomerDto(long id, String name, String email) {} public record CreateCustomerRequest( @NotBlank String name, @NotBlank String email ) {} }
✅ 2) Service — business rules + transaction boundary
package com.example.customer.service; import com.example.customer.persistence.Customer; import com.example.customer.persistence.CustomerDao; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import java.util.Optional; @ApplicationScoped public class CustomerService { @Inject CustomerDao dao; public Optional<Customer> getById(long id) { return dao.findById(id); } @Transactional public Customer create(String name, String email) { // business rules live here ✅ var customer = new Customer(); customer.setName(name.trim()); customer.setEmail(email.trim().toLowerCase()); dao.persist(customer); return customer; } }
✅ 3) DAO — persistence logic (JPA / EntityManager)
package com.example.customer.persistence; import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import java.util.Optional; @ApplicationScoped public class CustomerDao { @PersistenceContext EntityManager em; public Optional<Customer> findById(long id) { return Optional.ofNullable(em.find(Customer.class, id)); } public void persist(Customer customer) { em.persist(customer); } }
✅ 4) Entity — your database model
package com.example.customer.persistence; import jakarta.persistence.*; @Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 120) private String name; @Column(nullable = false, unique = true, length = 180) private String email; public Long getId() { return id; } public String getName() { return name; } public String getEmail() { return email; } public void setName(String name) { this.name = name; } public void setEmail(String email) { this.email = email; } }
🔸 TAKEAWAYS ✅
▪️ Jakarta EE gives you “batteries included” building blocks: CDI, JAX-RS, JSON-B, Bean Validation, JPA, Transactions ⚙️
▪️ Keep boundaries sharp: HTTP in the controller, rules in the service, SQL/JPA in the DAO 🧱
▪️ For microservice-to-microservice calls, wrap the client: add timeouts, headers, tracing, retries (where appropriate) 🔐⏱️
▪️ This layering makes code easier to test, refactor, and scale with teams 📈
#JakartaEE #Microservices #Java #JAXRS #CDI #JPA #REST #Backend #SoftwareArchitecture #CleanCode #CloudNative
Go further with Java certification:
Java👇
Spring👇
SpringBook👇
JavaBook👇