Skip to content

Instantly share code, notes, and snippets.

@fResult
Last active April 20, 2025 13:29
Show Gist options
  • Select an option

  • Save fResult/201b2384c03d44cc2f271f94028732b8 to your computer and use it in GitHub Desktop.

Select an option

Save fResult/201b2384c03d44cc2f271f94028732b8 to your computer and use it in GitHub Desktop.
Optional Demo for the article: https://medium.com/p/03845513d9f5 (You can see the whole code block at the file in the bottommost.)
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
private final CustomerOrderService customerOrderService;
OrderService(
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService,
CustomerOrderService customerOrderService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
this.customerOrderService = customerOrderService;
}
public OrderSummary processOrder(String orderId) {
System.out.println("[INFO] Processing order: " + orderId);
final var orderOpt = orderRepository.findById(orderId);
var orderContextOpt = orderOpt
.flatMap(customerOrderService::fetchOrderWithCustomer)
.flatMap(this::buildOrderContextWithValidatedPayment);
// Unfortunately, in Java, `ifPresent()` method doesn't return any object, so that we cannot continue chaining
orderContextOpt.ifPresent(notificationService::handleOrderNotification);
return orderContextOpt
.map(this::buildSummary)
.orElseThrow(ExceptionUtils.throwNotFound(Order.class, orderId));
}
private Optional<OrderContext> buildOrderContextWithValidatedPayment(
OrderWithCustomer orderWithCustomer) {
var contextOpt = paymentService
.fetchPaymentByCustomerId(orderWithCustomer.customer().id())
.filter(PaymentDetails::isSuccessful)
.map(this.combinePaymentWithOrder(orderWithCustomer));
contextOpt.orElseThrow(ExceptionUtils.throwPaymentFailed(orderWithCustomer.order().id()));
return contextOpt;
}
private Function<PaymentDetails, OrderContext> combinePaymentWithOrder(OrderWithCustomer orderWithCustomer) {
return payment -> new OrderContext(orderWithCustomer, payment);
}
private OrderSummary buildSummary(OrderContext context) {
return new OrderSummary(context.order(), context.customer(), context.payment());
}
}
public class ScratchOptional {
public static void main(String[] args) {
final var orderService = buildOrderService();
// <- Assume we handle this by `@ControllerAdvice`
try {
// TRYIT: Change orderId to be `order-2` instead of `order-1` to see PaymentFailed error occurs
final var orderSummary = orderService.processOrder("order-1");
System.out.println("Order Summary: " + orderSummary);
} catch (ElementNotFoundException ex) {
System.out.println("[WARN]: " + ex.getMessage());
} catch (PaymentFailedException ex) {
System.err.println("[ERROR]: " + ex.getMessage());
} catch (Exception ex) {
System.err.println("[Error]: Something went wrong! - " + ex.getMessage());
}
// Assume we handle this by `@ControllerAdvice` ->
}
public static OrderService buildOrderService() {
final var orderRepository = new OrderRepository();
final var customerRepository = new CustomerRepository();
final var paymentRepository = new PaymentRepository();
final var notificationService = new NotificationService();
final var paymentService = new PaymentService(paymentRepository);
final var customerOrderService = new CustomerOrderService(customerRepository);
return new OrderService(
orderRepository, paymentService, notificationService, customerOrderService);
}
}
// Basic domain record
public record Customer(String id, String email) {}
public class CustomerOrderService {
private final CustomerRepository customerRepository;
CustomerOrderService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public Optional<OrderWithCustomer> fetchOrderWithCustomer(Order order) {
final var orderWithCustomer = customerRepository
.findById(order.customerId())
.map(OrderWithCustomer.fromCustomerAndOrder(order))
.orElseThrow(ExceptionUtils.throwNotFound(Customer.class, order.customerId()));
return Optional.ofNullable(orderWithCustomer);
}
}
public class CustomerRepository {
private static final Map<String, Customer> data = new HashMap<>() {{
put("cust-1", new Customer("cust-1", "email1@example.com"));
put("cust-2", new Customer("cust-2", "email2@example.com"));
}};
Optional<Customer> findById(String customerId) {
return Optional.of(data.get(customerId));
}
}
public class ElementNotFoundException extends RuntimeException {
public ElementNotFoundException(String message) {
super(message);
}
}
final class ExceptionUtils {
public static Supplier<ElementNotFoundException> throwNotFound(Class<?> resouceClass, String resourceId) {
return () -> new ElementNotFoundException(resouceClass.getSimpleName() + " with id [" + resourceId + "] not found");
}
public static Supplier<ElementNotFoundException> throwSubResourceNotFound(Class<?> resourceClass, Class<?> subResourceClass, String resourceId) {
final var errorMessage = String.format("%s for %s [%s] not found", subResourceClass.getSimpleName(), resourceClass.getSimpleName(), resourceId);
return () -> new ElementNotFoundException(errorMessage);
}
public static Supplier<PaymentFailedException> throwPaymentFailed(String orderId) {
final var errorMessage = String.format("Payment unsuccessful for Order with id [%s]", orderId);
return () -> new PaymentFailedException(errorMessage);
}
}
public class NotificationException extends RuntimeException {
public NotificationException(String message) {
super(message);
}
}
public class NotificationService {
public void handleOrderNotification(OrderContext context) {
try {
this.sendConfirmation(context.customer().email(), context.order().details());
} catch (NotificationException e) {
final var errorMessage = "Failed to send notification, but order processed: " + e.getMessage();
// We continue processing since this is non-critical
System.out.println("[WARN]: " + errorMessage);
}
}
private void sendConfirmation(String email, OrderDetails details) throws NotificationException {
System.out.println("Notification: Notified to [" + email + "] with " + details);
// TRYIT: Uncomment code below to mock this method as error occurrence
// final var errorMessage = String.format("Email cannot reach customer with email: [%s] by some reason", email);
// throw new NotificationException(errorMessage);
}
}
// Basic domain record
public record Order(String id, String customerId, OrderDetails details) {}
// Composite record for the processing flow
public record OrderContext(OrderWithCustomer orderWithCustomer, PaymentDetails payment) {
// Convenience methods to access nested properties
public Order order() {
return orderWithCustomer.order();
}
public Customer customer() {
return orderWithCustomer.customer();
}
}
public record OrderDetails(String productId, int quantity, double amount) {}
// Fake Repository class
public class OrderRepository {
private static final Map<String, Order> data = new HashMap<>() {{
put("order-1", new Order("order-1", "cust-1", new OrderDetails("detail-1", 10, 1000)));
put("order-2", new Order("order-2", "cust-2", new OrderDetails("detail-2", 50, 500)));
}};
Optional<Order> findById(String orderId) {
return Optional.ofNullable(data.get(orderId));
}
}
// Composite record for the processing flow
public record OrderSummary(Order order, Customer customer, PaymentDetails payment) {}
// Composite record for the processing flow
public record OrderWithCustomer(Order order, Customer customer) {
public static Function<Customer, OrderWithCustomer> fromCustomerAndOrder(Order order) {
return customer -> new OrderWithCustomer(order, customer);
}
}
public record PaymentDetails(String id, String orderId, boolean isSuccessful) {}
public class PaymentFailedException extends RuntimeException {
public PaymentFailedException(String message) {
super(message);
}
}
public class PaymentRepository {
private static final Map<String, PaymentDetails> data = new HashMap<>() {{
put("cust-1", new PaymentDetails("payment-1", "order-1", true));
put("cust-2", new PaymentDetails("payment-2", "order-2", false));
}};
public Optional<PaymentDetails> findPaymentByCustomerId(String orderId) {
return Optional.ofNullable(data.get(orderId));
}
}
public class PaymentService {
private final PaymentRepository paymentRepository;
PaymentService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
public Optional<PaymentDetails> fetchPaymentByCustomerId(String customerId) {
final var paymentOpt = paymentRepository.findPaymentByCustomerId(customerId);
paymentOpt.orElseThrow(ExceptionUtils.throwSubResourceNotFound(
Customer.class, PaymentDetails.class, customerId));
return paymentOpt;
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
class OptionalDemo {
public static void main(String[] args) {
final var orderService = buildOrderService();
// <- Assume we handle this by `@ControllerAdvice`
try {
// TRYIT: Change orderId to be `order-2` instead of `order-1` to see PaymentFailed error occurs
final var orderSummary = orderService.processOrder("order-1");
System.out.println("Order Summary: " + orderSummary);
} catch (ElementNotFoundException ex) {
System.out.println("[WARN]: " + ex.getMessage());
} catch (PaymentFailedException ex) {
System.err.println("[ERROR]: " + ex.getMessage());
} catch (Exception ex) {
System.err.println("[Error]: Something went wrong! - " + ex.getMessage());
}
// Assume we handle this by `@ControllerAdvice` ->
}
public static OrderService buildOrderService() {
final var orderRepository = new OrderRepository();
final var customerRepository = new CustomerRepository();
final var paymentRepository = new PaymentRepository();
final var notificationService = new NotificationService();
final var paymentService = new PaymentService(paymentRepository);
final var customerOrderService = new CustomerOrderService(customerRepository);
return new OrderService(
orderRepository, paymentService, notificationService, customerOrderService);
}
}
class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
private final CustomerOrderService customerOrderService;
OrderService(
OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService,
CustomerOrderService customerOrderService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
this.customerOrderService = customerOrderService;
}
public OrderSummary processOrder(String orderId) {
System.out.println("[INFO] Processing order: " + orderId);
final var orderOpt = orderRepository.findById(orderId);
var orderContextOpt = orderOpt
.flatMap(customerOrderService::fetchOrderWithCustomer)
.flatMap(this::buildOrderContextWithValidatedPayment);
// Unfortunately, in Java, `ifPresent()` method doesn't return any object, so that we cannot continue chaining
orderContextOpt.ifPresent(notificationService::handleOrderNotification);
return orderContextOpt
.map(this::buildSummary)
.orElseThrow(ExceptionUtils.throwNotFound(Order.class, orderId));
}
private Optional<OrderContext> buildOrderContextWithValidatedPayment(
OrderWithCustomer orderWithCustomer) {
var contextOpt = paymentService
.fetchPaymentByCustomerId(orderWithCustomer.customer().id())
.filter(PaymentDetails::isSuccessful)
.map(this.combinePaymentWithOrder(orderWithCustomer));
contextOpt.orElseThrow(ExceptionUtils.throwPaymentFailed(orderWithCustomer.order().id()));
return contextOpt;
}
private Function<PaymentDetails, OrderContext> combinePaymentWithOrder(OrderWithCustomer orderWithCustomer) {
return payment -> new OrderContext(orderWithCustomer, payment);
}
private OrderSummary buildSummary(OrderContext context) {
return new OrderSummary(context.order(), context.customer(), context.payment());
}
}
// Basic domain records
record Order(String id, String customerId, OrderDetails details) {}
record Customer(String id, String email) {}
record PaymentDetails(String id, String orderId, boolean isSuccessful) {}
record OrderDetails(String productId, int quantity, double amount) {}
// Composite records for the processing flow
record OrderWithCustomer(Order order, Customer customer) {
public static Function<Customer, OrderWithCustomer> fromCustomerAndOrder(Order order) {
return customer -> new OrderWithCustomer(order, customer);
}
}
record OrderContext(OrderWithCustomer orderWithCustomer, PaymentDetails payment) {
// Convenience methods to access nested properties
public Order order() {
return orderWithCustomer.order();
}
public Customer customer() {
return orderWithCustomer.customer();
}
}
record OrderSummary(Order order, Customer customer, PaymentDetails payment) {}
// Exception classes
class ElementNotFoundException extends RuntimeException {
public ElementNotFoundException(String message) {
super(message);
}
}
class PaymentFailedException extends RuntimeException {
public PaymentFailedException(String message) {
super(message);
}
}
class NotificationException extends RuntimeException {
public NotificationException(String message) {
super(message);
}
}
// Repository
class OrderRepository {
private static final Map<String, Order> data = new HashMap<>() {{
put("order-1", new Order("order-1", "cust-1", new OrderDetails("detail-1", 10, 1000)));
put("order-2", new Order("order-2", "cust-2", new OrderDetails("detail-2", 50, 500)));
}};
Optional<Order> findById(String orderId) {
return Optional.ofNullable(data.get(orderId));
}
}
class CustomerRepository {
private static final Map<String, Customer> data = new HashMap<>() {{
put("cust-1", new Customer("cust-1", "email1@example.com"));
put("cust-2", new Customer("cust-2", "email2@example.com"));
}};
Optional<Customer> findById(String customerId) {
return Optional.of(data.get(customerId));
}
}
class PaymentService {
private final PaymentRepository paymentRepository;
PaymentService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
public Optional<PaymentDetails> fetchPaymentByCustomerId(String customerId) {
final var paymentOpt = paymentRepository.findPaymentByCustomerId(customerId);
paymentOpt.orElseThrow(ExceptionUtils.throwSubResourceNotFound(
Customer.class, PaymentDetails.class, customerId));
// Unfortunately, in Java, `orElseThrow()` method returns `R` instead of of `Optional<R>`, so that we cannot continue chaining
return paymentOpt;
}
}
class NotificationService {
public void handleOrderNotification(OrderContext context) {
try {
this.sendConfirmation(context.customer().email(), context.order().details());
} catch (NotificationException e) {
final var errorMessage = "Failed to send notification, but order processed: " + e.getMessage();
// We continue processing since this is non-critical
System.out.println("[WARN]: " + errorMessage);
}
}
private void sendConfirmation(String email, OrderDetails details) throws NotificationException {
System.out.println("Notification: Notified to [" + email + "] with " + details);
// TRYIT: Uncomment code below to mock this method as error occurrence
// final var errorMessage = String.format("Email cannot reach customer with email: [%s] by some reason", email);
// throw new NotificationException(errorMessage);
}
}
// Service classes
class CustomerOrderService {
private final CustomerRepository customerRepository;
CustomerOrderService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
public Optional<OrderWithCustomer> fetchOrderWithCustomer(Order order) {
final var orderWithCustomer = customerRepository
.findById(order.customerId())
.map(OrderWithCustomer.fromCustomerAndOrder(order))
.orElseThrow(ExceptionUtils.throwNotFound(Customer.class, order.customerId()));
// Unfortunately, in Java, `orElseThrow()` method returns `R` instead of of `Optional<R>`, so that we cannot continue chaining
return Optional.ofNullable(orderWithCustomer);
}
}
class PaymentRepository {
private static final Map<String, PaymentDetails> data = new HashMap<>() {{
put("cust-1", new PaymentDetails("payment-1", "order-1", true));
put("cust-2", new PaymentDetails("payment-2", "order-2", false));
}};
public Optional<PaymentDetails> findPaymentByCustomerId(String customerId) {
return Optional.ofNullable(data.get(customerId));
}
}
final class ExceptionUtils {
public static Supplier<ElementNotFoundException> throwNotFound(Class<?> resouceClass, String resourceId) {
return () -> new ElementNotFoundException(resouceClass.getSimpleName() + " with id [" + resourceId + "] not found");
}
public static Supplier<ElementNotFoundException> throwSubResourceNotFound(Class<?> resourceClass, Class<?> subResourceClass, String resourceId) {
final var errorMessage = String.format("%s for %s [%s] not found", subResourceClass.getSimpleName(), resourceClass.getSimpleName(), resourceId);
return () -> new ElementNotFoundException(errorMessage);
}
public static Supplier<PaymentFailedException> throwPaymentFailed(String orderId) {
final var errorMessage = String.format("Payment unsuccessful for Order with id [%s]", orderId);
return () -> new PaymentFailedException(errorMessage);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment