- Published on
The Complete Guide to Modern Java Architecture - Part 3: Implementation Deep Dives
- Authors
- Name
- Gary Huynh
- @gary_atruedev
The Complete Guide to Modern Java Architecture - Part 3: Implementation Deep Dives
This is Part 3 of a comprehensive 5-part series on Modern Java Architecture. Building on the architectural patterns from Part 2, we now dive deep into the implementation details that make systems production-ready.
Series Overview:
- Part 1: Foundation - Evolution, principles, and modern Java features
- Part 2: Architecture Patterns - Monoliths, microservices, and event-driven design
- Part 3: Implementation Deep Dives (This post) - APIs, data layer, security, and observability
- Part 4: Performance & Scalability - Optimization, reactive programming, and scaling patterns
- Part 5: Production Considerations - Deployment, containers, and operational excellence
Architecture patterns provide the blueprint, but implementation details determine whether your system succeeds in production. After years of debugging midnight outages, performance bottlenecks, and security incidents, I've learned that robust implementation is what separates theoretical architectures from production-ready systems.
This part focuses on the four pillars of implementation excellence: API design, data management, security, and observability. These aren't separate concerns—they're interconnected aspects that must work together seamlessly.
API Design: Your System's Contract
API-First Development in 2025
Modern systems start with API contracts, not implementation. This approach enables parallel development, clear boundaries, and evolutionary design.
# OpenAPI 3.1 specification
openapi: 3.1.0
info:
title: Order Management API
version: 2.0.0
description: Comprehensive order management with event-driven capabilities
servers:
- url: https://api.example.com/v2
description: Production server
- url: https://staging-api.example.com/v2
description: Staging server
paths:
/orders:
post:
operationId: createOrder
summary: Create a new order
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
examples:
simple_order:
summary: Simple product order
value:
customerId: "cust_12345"
items:
- productId: "prod_67890"
quantity: 2
unitPrice: 29.99
responses:
'201':
description: Order created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
headers:
Location:
description: URL of the created order
schema:
type: string
format: uri
'400':
$ref: '#/components/responses/ValidationError'
'409':
$ref: '#/components/responses/ConflictError'
components:
schemas:
CreateOrderRequest:
type: object
required: [customerId, items]
properties:
customerId:
type: string
pattern: '^cust_[a-zA-Z0-9]+$'
example: "cust_12345"
items:
type: array
minItems: 1
maxItems: 50
items:
$ref: '#/components/schemas/OrderItem'
shippingAddress:
$ref: '#/components/schemas/Address'
metadata:
type: object
additionalProperties:
type: string
maxProperties: 10
additionalProperties: false
Generated Code with Modern Java Features:
// Generated from OpenAPI specification
public record CreateOrderRequest(
@Pattern(regexp = "^cust_[a-zA-Z0-9]+$")
@NotNull String customerId,
@Valid @NotEmpty @Size(max = 50)
List<OrderItem> items,
@Valid Address shippingAddress,
@Size(max = 10)
Map<String, String> metadata
) {
public CreateOrderRequest {
// Compact constructor validation
Objects.requireNonNull(customerId, "Customer ID cannot be null");
Objects.requireNonNull(items, "Items cannot be null");
if (items.isEmpty()) {
throw new IllegalArgumentException("Order must contain at least one item");
}
}
}
// Domain-driven implementation
@RestController
@RequestMapping("/api/v2/orders")
@Validated
public class OrderController {
private final OrderService orderService;
private final OrderMapper orderMapper;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<OrderResponse> createOrder(
@Valid @RequestBody CreateOrderRequest request,
HttpServletRequest httpRequest) {
// Convert to domain command
CreateOrderCommand command = orderMapper.toCommand(request);
// Execute business logic
Order order = orderService.createOrder(command);
// Convert to response DTO
OrderResponse response = orderMapper.toResponse(order);
// Build response with proper headers
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(order.getId())
.toUri();
return ResponseEntity
.created(location)
.header("X-Request-ID", httpRequest.getHeader("X-Request-ID"))
.body(response);
}
}
API Versioning Strategies
Semantic Versioning with Backward Compatibility:
// Version-aware controller
@RestController
@RequestMapping("/api")
public class VersionedOrderController {
// Version 1 - Legacy support
@PostMapping(value = "/v1/orders",
produces = "application/vnd.api.v1+json")
public OrderV1Response createOrderV1(@RequestBody CreateOrderV1Request request) {
// Convert V1 request to current domain model
CreateOrderCommand command = v1Adapter.adapt(request);
Order order = orderService.createOrder(command);
return v1Adapter.adapt(order);
}
// Version 2 - Current
@PostMapping(value = "/v2/orders",
produces = "application/vnd.api.v2+json")
public OrderV2Response createOrderV2(@RequestBody CreateOrderV2Request request) {
CreateOrderCommand command = v2Adapter.adapt(request);
Order order = orderService.createOrder(command);
return v2Adapter.adapt(order);
}
// Future version - with feature flags
@PostMapping(value = "/v3/orders",
produces = "application/vnd.api.v3+json")
@ConditionalOnProperty(name = "api.v3.enabled", havingValue = "true")
public OrderV3Response createOrderV3(@RequestBody CreateOrderV3Request request) {
// New features available in V3
CreateOrderCommand command = v3Adapter.adapt(request);
Order order = orderService.createOrder(command);
return v3Adapter.adapt(order);
}
}
// Adapter pattern for version translation
@Component
public class OrderV1Adapter {
public CreateOrderCommand adapt(CreateOrderV1Request v1Request) {
return CreateOrderCommand.builder()
.customerId(v1Request.customerId())
.items(adaptItems(v1Request.items()))
// V1 didn't have shipping address - use default
.shippingAddress(getDefaultShippingAddress(v1Request.customerId()))
// V1 didn't have metadata - empty map
.metadata(Collections.emptyMap())
.build();
}
public OrderV1Response adapt(Order order) {
return new OrderV1Response(
order.getId().toString(),
order.getCustomerId(),
adaptItemsToV1(order.getItems()),
order.getTotal().getAmount(),
order.getStatus().name()
);
}
}
Modern API Patterns
GraphQL for Complex Data Requirements:
// GraphQL schema definition
@Component
public class OrderGraphQLResolver implements GraphQLQueryResolver, GraphQLMutationResolver {
private final OrderService orderService;
private final CustomerService customerService;
// Query resolver with DataLoader for N+1 prevention
public List<Order> getOrders(String customerId,
OrderStatus status,
DataFetchingEnvironment env) {
DataLoader<String, Customer> customerLoader =
env.getDataLoader("customerLoader");
return orderService.findOrders(customerId, status)
.stream()
.peek(order -> customerLoader.load(order.getCustomerId()))
.collect(toList());
}
// Mutation with optimistic locking
public Order updateOrderStatus(String orderId,
OrderStatus newStatus,
Integer version) {
UpdateOrderStatusCommand command = new UpdateOrderStatusCommand(
OrderId.of(orderId), newStatus, version);
return orderService.updateStatus(command);
}
// Subscription for real-time updates
@SchemaMapping(typeName = "Subscription", field = "orderUpdates")
public Publisher<Order> orderUpdates(@Argument String customerId) {
return orderEventPublisher
.getOrderUpdatesFor(customerId)
.map(this::convertToOrder);
}
}
// DataLoader configuration for efficient batching
@Configuration
public class GraphQLDataLoaderConfig {
@Bean
public DataLoader<String, Customer> customerDataLoader(CustomerService customerService) {
return DataLoaderFactory.newDataLoader(customerIds ->
CompletableFuture.supplyAsync(() ->
customerService.findByIds(customerIds)));
}
}
gRPC for Internal Service Communication:
// order_service.proto
syntax = "proto3";
package com.example.orders;
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (Order);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc StreamOrderUpdates(StreamOrderUpdatesRequest) returns (stream OrderUpdate);
}
message CreateOrderRequest {
string customer_id = 1;
repeated OrderItem items = 2;
Address shipping_address = 3;
map<string, string> metadata = 4;
}
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
OrderStatus status = 4;
google.protobuf.Timestamp created_at = 5;
Money total = 6;
}
// gRPC service implementation
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
private final OrderService orderService;
private final OrderProtoMapper mapper;
@Override
public void createOrder(CreateOrderRequest request,
StreamObserver<Order> responseObserver) {
try {
// Convert from protobuf to domain
CreateOrderCommand command = mapper.toDomain(request);
// Execute business logic
com.example.domain.Order domainOrder = orderService.createOrder(command);
// Convert back to protobuf
Order protoOrder = mapper.toProto(domainOrder);
responseObserver.onNext(protoOrder);
responseObserver.onCompleted();
} catch (ValidationException e) {
responseObserver.onError(Status.INVALID_ARGUMENT
.withDescription(e.getMessage())
.asRuntimeException());
} catch (Exception e) {
responseObserver.onError(Status.INTERNAL
.withDescription("Internal server error")
.asRuntimeException());
}
}
@Override
public void streamOrderUpdates(StreamOrderUpdatesRequest request,
StreamObserver<OrderUpdate> responseObserver) {
Flux<OrderUpdate> updateStream = orderEventStream
.getUpdatesFor(request.getCustomerId())
.map(mapper::toOrderUpdate)
.doOnCancel(() -> log.info("Client cancelled stream"))
.doOnError(error -> log.error("Stream error", error));
updateStream.subscribe(
responseObserver::onNext,
responseObserver::onError,
responseObserver::onCompleted
);
}
}
Data Layer: Consistency and Performance
Multi-Model Data Architecture
Modern applications require different data models for different use cases:
// Command side - Optimized for writes
@Entity
@Table(name = "orders")
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "customer_id", nullable = false)
private String customerId;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private List<OrderItemEntity> items = new ArrayList<>();
@Enumerated(EnumType.STRING)
private OrderStatus status;
@Version
private Integer version;
@CreationTimestamp
private Instant createdAt;
@UpdateTimestamp
private Instant updatedAt;
// Domain logic methods
public void addItem(OrderItemEntity item) {
items.add(item);
item.setOrder(this);
}
public Money calculateTotal() {
return items.stream()
.map(OrderItemEntity::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
}
// Query side - Optimized for reads
@Document(collection = "order_views")
public class OrderView {
@Id
private String id;
@Indexed
private String customerId;
private String customerName;
private String customerEmail;
private List<OrderItemView> items;
private BigDecimal totalAmount;
private String currency;
private OrderStatus status;
@Indexed
private LocalDateTime createdAt;
// Denormalized for fast queries
private CustomerSummary customerSummary;
private List<String> productCategories;
private Map<String, Object> searchableAttributes;
}
// Repository pattern with multiple implementations
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(OrderId id);
List<Order> findByCustomerId(String customerId);
}
@Repository
@Transactional
public class JpaOrderRepository implements OrderRepository {
private final OrderJpaRepository jpaRepository;
private final OrderEntityMapper mapper;
@Override
public Order save(Order order) {
OrderEntity entity = mapper.toEntity(order);
OrderEntity saved = jpaRepository.save(entity);
return mapper.toDomain(saved);
}
@Override
@Transactional(readOnly = true)
public Optional<Order> findById(OrderId id) {
return jpaRepository.findById(id.getValue())
.map(mapper::toDomain);
}
}
// Read-optimized repository
@Repository
public class MongoOrderViewRepository {
private final MongoTemplate mongoTemplate;
public List<OrderView> findByCustomerIdWithPagination(String customerId,
Pageable pageable) {
Query query = Query.query(Criteria.where("customerId").is(customerId))
.with(pageable)
.with(Sort.by(Direction.DESC, "createdAt"));
return mongoTemplate.find(query, OrderView.class);
}
public List<OrderView> searchOrders(OrderSearchCriteria criteria) {
Criteria searchCriteria = new Criteria();
if (criteria.getCustomerName() != null) {
searchCriteria.and("customerName")
.regex(criteria.getCustomerName(), "i");
}
if (criteria.getStatus() != null) {
searchCriteria.and("status").is(criteria.getStatus());
}
if (criteria.getDateRange() != null) {
searchCriteria.and("createdAt")
.gte(criteria.getDateRange().getStart())
.lte(criteria.getDateRange().getEnd());
}
Query query = Query.query(searchCriteria);
return mongoTemplate.find(query, OrderView.class);
}
}
Event Sourcing and CQRS Implementation
// Event Store implementation
@Component
public class EventStore {
private final EventJpaRepository eventRepository;
private final EventSerializer eventSerializer;
public void saveEvents(String aggregateId,
List<DomainEvent> events,
int expectedVersion) {
// Optimistic locking check
int currentVersion = getCurrentVersion(aggregateId);
if (currentVersion != expectedVersion) {
throw new ConcurrencyException(
"Expected version " + expectedVersion +
" but was " + currentVersion);
}
// Save events
List<EventEntity> eventEntities = events.stream()
.map(event -> EventEntity.builder()
.aggregateId(aggregateId)
.eventType(event.getClass().getSimpleName())
.eventData(eventSerializer.serialize(event))
.eventVersion(++currentVersion)
.timestamp(Instant.now())
.build())
.collect(toList());
eventRepository.saveAll(eventEntities);
// Publish events for projections
events.forEach(eventPublisher::publish);
}
public Stream<DomainEvent> getEvents(String aggregateId) {
return eventRepository.findByAggregateIdOrderByEventVersion(aggregateId)
.stream()
.map(entity -> eventSerializer.deserialize(
entity.getEventData(), entity.getEventType()));
}
}
// Aggregate root with event sourcing
public class Order {
private OrderId id;
private String customerId;
private List<OrderItem> items = new ArrayList<>();
private OrderStatus status;
private int version;
// Events to be saved
private List<DomainEvent> uncommittedEvents = new ArrayList<>();
// Apply events for state reconstruction
public void apply(DomainEvent event) {
switch (event) {
case OrderCreatedEvent e -> {
this.id = e.orderId();
this.customerId = e.customerId();
this.items = new ArrayList<>(e.items());
this.status = OrderStatus.PENDING;
}
case OrderItemAddedEvent e -> {
this.items.add(e.item());
}
case OrderConfirmedEvent e -> {
this.status = OrderStatus.CONFIRMED;
}
case OrderCancelledEvent e -> {
this.status = OrderStatus.CANCELLED;
}
default -> throw new IllegalArgumentException("Unknown event: " + event);
}
version++;
}
// Command handlers that generate events
public static Order create(CreateOrderCommand command) {
Order order = new Order();
OrderCreatedEvent event = new OrderCreatedEvent(
command.orderId(),
command.customerId(),
command.items(),
Instant.now()
);
order.apply(event);
order.uncommittedEvents.add(event);
return order;
}
public void confirm() {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Only pending orders can be confirmed");
}
OrderConfirmedEvent event = new OrderConfirmedEvent(id, Instant.now());
apply(event);
uncommittedEvents.add(event);
}
}
// Projection handler for read models
@Component
public class OrderProjectionHandler {
private final OrderViewRepository orderViewRepository;
private final CustomerService customerService;
@EventHandler
@Async
public void handle(OrderCreatedEvent event) {
// Build denormalized view
Customer customer = customerService.findById(event.customerId());
OrderView view = OrderView.builder()
.id(event.orderId().toString())
.customerId(event.customerId())
.customerName(customer.getName())
.customerEmail(customer.getEmail())
.items(event.items().stream()
.map(this::toOrderItemView)
.collect(toList()))
.status(OrderStatus.PENDING)
.createdAt(event.occurredAt().atZone(ZoneOffset.UTC).toLocalDateTime())
.searchableAttributes(buildSearchableAttributes(event, customer))
.build();
orderViewRepository.save(view);
}
@EventHandler
@Async
public void handle(OrderConfirmedEvent event) {
orderViewRepository.updateStatus(
event.orderId().toString(),
OrderStatus.CONFIRMED
);
}
}
Distributed Transaction Patterns
// Saga pattern implementation
@Component
public class OrderProcessingSaga {
private final SagaManager sagaManager;
private final PaymentService paymentService;
private final InventoryService inventoryService;
private final OrderService orderService;
@SagaStart
public void handle(OrderCreatedEvent event) {
SagaTransaction saga = sagaManager.begin(
"order-processing",
event.orderId().toString()
);
// Step 1: Reserve inventory
saga.step("reserve-inventory")
.invoke(() -> inventoryService.reserve(event.items()))
.compensate(() -> inventoryService.release(event.items()));
// Step 2: Process payment
saga.step("process-payment")
.invoke(() -> paymentService.charge(event.paymentDetails()))
.compensate(() -> paymentService.refund(event.paymentDetails()));
// Step 3: Confirm order
saga.step("confirm-order")
.invoke(() -> orderService.confirm(event.orderId()))
.compensate(() -> orderService.cancel(event.orderId(), "Saga rollback"));
saga.execute();
}
}
// Outbox pattern for reliable event publishing
@Component
@Transactional
public class OutboxEventPublisher {
private final OutboxRepository outboxRepository;
public void publishEvent(DomainEvent event, String aggregateId) {
// Save to outbox table in same transaction
OutboxEvent outboxEvent = OutboxEvent.builder()
.aggregateId(aggregateId)
.eventType(event.getClass().getSimpleName())
.eventData(serialize(event))
.createdAt(Instant.now())
.processed(false)
.build();
outboxRepository.save(outboxEvent);
}
}
// Outbox processor - separate component
@Component
public class OutboxProcessor {
@Scheduled(fixedDelay = 5000) // Every 5 seconds
@Transactional
public void processOutboxEvents() {
List<OutboxEvent> unprocessedEvents = outboxRepository
.findByProcessedFalseOrderByCreatedAt(PageRequest.of(0, 100));
for (OutboxEvent outboxEvent : unprocessedEvents) {
try {
DomainEvent event = deserialize(
outboxEvent.getEventData(),
outboxEvent.getEventType()
);
// Publish to message broker
eventPublisher.publish(event);
// Mark as processed
outboxEvent.setProcessed(true);
outboxRepository.save(outboxEvent);
} catch (Exception e) {
log.error("Failed to process outbox event: {}",
outboxEvent.getId(), e);
// Retry logic or dead letter queue
}
}
}
}
Security: Defense in Depth
Authentication and Authorization
// JWT-based authentication with modern Spring Security
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/api/orders").hasRole("USER")
.requestMatchers(HttpMethod.GET, "/api/orders/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.decoder(jwtDecoder())
.jwtAuthenticationConverter(jwtAuthenticationConverter())))
.exceptionHandling(ex -> ex
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler()))
.build();
}
@Bean
public JwtDecoder jwtDecoder() {
return JwtDecoders.fromIssuerLocation("https://your-auth-server.com");
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
Collection<String> authorities = jwt.getClaimAsStringList("authorities");
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
});
return converter;
}
}
// Method-level security with SpEL
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN') or #customerId == authentication.name")
public List<Order> getOrdersForCustomer(String customerId) {
return orderRepository.findByCustomerId(customerId);
}
@PreAuthorize("@orderSecurityService.canAccessOrder(#orderId, authentication)")
public Order getOrder(OrderId orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
}
@PostAuthorize("@orderSecurityService.filterSensitiveData(returnObject, authentication)")
public Order getOrderWithFiltering(OrderId orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
}
}
// Custom security service
@Component
public class OrderSecurityService {
public boolean canAccessOrder(OrderId orderId, Authentication auth) {
if (hasRole(auth, "ADMIN")) {
return true;
}
Order order = orderRepository.findById(orderId).orElse(null);
return order != null && order.getCustomerId().equals(auth.getName());
}
public Order filterSensitiveData(Order order, Authentication auth) {
if (hasRole(auth, "ADMIN")) {
return order; // Admins see everything
}
// Filter sensitive data for regular users
return order.toBuilder()
.paymentDetails(null) // Hide payment details
.internalNotes(null) // Hide internal notes
.build();
}
private boolean hasRole(Authentication auth, String role) {
return auth.getAuthorities().stream()
.anyMatch(grantedAuthority ->
grantedAuthority.getAuthority().equals("ROLE_" + role));
}
}
Data Protection and Encryption
// Field-level encryption
@Component
public class FieldEncryption {
private final AESUtil aesUtil;
@EventListener
public void encryptSensitiveFields(BeforeSaveEvent<OrderEntity> event) {
OrderEntity order = event.getSource();
// Encrypt PII fields
if (order.getCustomerEmail() != null) {
order.setCustomerEmail(aesUtil.encrypt(order.getCustomerEmail()));
}
if (order.getBillingAddress() != null) {
order.setBillingAddress(aesUtil.encrypt(order.getBillingAddress()));
}
}
@EventListener
public void decryptSensitiveFields(AfterLoadEvent<OrderEntity> event) {
OrderEntity order = event.getSource();
// Decrypt PII fields
if (order.getCustomerEmail() != null) {
order.setCustomerEmail(aesUtil.decrypt(order.getCustomerEmail()));
}
if (order.getBillingAddress() != null) {
order.setBillingAddress(aesUtil.decrypt(order.getBillingAddress()));
}
}
}
// Secure configuration management
@ConfigurationProperties(prefix = "app.security")
@ConstructorBinding
public record SecurityConfig(
@Valid EncryptionConfig encryption,
@Valid JwtConfig jwt,
@Valid ApiSecurityConfig api
) {
public record EncryptionConfig(
@NotBlank String algorithm,
@NotBlank String keyId,
int keyRotationDays
) {}
public record JwtConfig(
@NotBlank String issuer,
Duration accessTokenExpiry,
Duration refreshTokenExpiry
) {}
public record ApiSecurityConfig(
List<String> allowedOrigins,
int rateLimitPerMinute,
boolean enableCsrfProtection
) {}
}
// Audit logging
@Aspect
@Component
public class SecurityAuditAspect {
private final AuditEventRepository auditEventRepository;
@AfterReturning(pointcut = "@annotation(PreAuthorize)", returning = "result")
public void auditSecurityOperation(JoinPoint joinPoint, Object result) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
AuditEvent auditEvent = AuditEvent.builder()
.timestamp(Instant.now())
.userId(auth.getName())
.operation(joinPoint.getSignature().getName())
.resource(extractResource(joinPoint))
.outcome(AuditOutcome.SUCCESS)
.details(buildAuditDetails(joinPoint, result))
.build();
auditEventRepository.save(auditEvent);
}
@AfterThrowing(pointcut = "@annotation(PreAuthorize)", throwing = "ex")
public void auditSecurityFailure(JoinPoint joinPoint, Exception ex) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
AuditEvent auditEvent = AuditEvent.builder()
.timestamp(Instant.now())
.userId(auth != null ? auth.getName() : "anonymous")
.operation(joinPoint.getSignature().getName())
.resource(extractResource(joinPoint))
.outcome(AuditOutcome.FAILURE)
.errorMessage(ex.getMessage())
.build();
auditEventRepository.save(auditEvent);
}
}
Observability: Production Insights
Distributed Tracing
// Manual instrumentation with OpenTelemetry
@Component
public class OrderService {
private final Tracer tracer;
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public Order createOrder(CreateOrderCommand command) {
Span span = tracer.spanBuilder("OrderService.createOrder")
.setSpanKind(SpanKind.INTERNAL)
.setAttribute("order.customerId", command.getCustomerId())
.setAttribute("order.itemCount", command.getItems().size())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Add custom events
span.addEvent("validation.started");
validateOrder(command);
span.addEvent("validation.completed");
// Create nested span for database operation
Order order;
try (Scope dbScope = createDatabaseSpan("order.save").makeCurrent()) {
order = orderRepository.save(Order.create(command));
}
// Async operation with context propagation
CompletableFuture.supplyAsync(() -> {
try (Scope asyncScope = span.makeCurrent()) {
return paymentService.authorizePayment(command.getPaymentDetails());
}
}, Context.taskWrapping(Executors.newCachedThreadPool()));
span.setStatus(StatusCode.OK);
return order;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private Span createDatabaseSpan(String operation) {
return tracer.spanBuilder("db.operation")
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.system", "postgresql")
.setAttribute("db.operation", operation)
.startSpan();
}
}
// Automatic instrumentation configuration
@Configuration
public class ObservabilityConfig {
@Bean
public OpenTelemetry openTelemetry() {
return OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(
OtlpGrpcSpanExporter.builder()
.setEndpoint("http://jaeger:14250")
.build())
.build())
.setResource(Resource.getDefault()
.merge(Resource.builder()
.put(ResourceAttributes.SERVICE_NAME, "order-service")
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.build()))
.build())
.buildAndRegisterGlobal();
}
@Bean
public Tracer tracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer("order-service");
}
}
Metrics and Monitoring
// Custom metrics with Micrometer
@Component
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Timer orderProcessingTimer;
private final Gauge activeOrdersGauge;
private final DistributionSummary orderValueSummary;
public OrderMetrics(MeterRegistry meterRegistry) {
this.orderCreatedCounter = Counter.builder("orders.created")
.description("Number of orders created")
.tag("service", "order-service")
.register(meterRegistry);
this.orderProcessingTimer = Timer.builder("orders.processing.duration")
.description("Time taken to process orders")
.register(meterRegistry);
this.activeOrdersGauge = Gauge.builder("orders.active")
.description("Number of active orders")
.register(meterRegistry, this, OrderMetrics::getActiveOrderCount);
this.orderValueSummary = DistributionSummary.builder("orders.value")
.description("Distribution of order values")
.baseUnit("USD")
.register(meterRegistry);
}
public void recordOrderCreated(Order order) {
orderCreatedCounter.increment(
Tags.of(
"customer.type", order.getCustomerType(),
"order.channel", order.getChannel()
)
);
orderValueSummary.record(order.getTotalAmount().doubleValue());
}
public Timer.Sample startOrderProcessing() {
return Timer.start(orderProcessingTimer);
}
private double getActiveOrderCount() {
return orderRepository.countByStatus(OrderStatus.PROCESSING);
}
}
// Health checks
@Component
public class OrderServiceHealthIndicator implements HealthIndicator {
private final OrderRepository orderRepository;
private final PaymentServiceClient paymentServiceClient;
@Override
public Health health() {
try {
// Check database connectivity
long orderCount = orderRepository.count();
// Check external service connectivity
boolean paymentServiceHealthy = paymentServiceClient.healthCheck();
if (paymentServiceHealthy) {
return Health.up()
.withDetail("database.orders", orderCount)
.withDetail("payment.service", "UP")
.withDetail("timestamp", Instant.now())
.build();
} else {
return Health.down()
.withDetail("payment.service", "DOWN")
.withDetail("timestamp", Instant.now())
.build();
}
} catch (Exception e) {
return Health.down()
.withException(e)
.withDetail("timestamp", Instant.now())
.build();
}
}
}
Logging Strategy
// Structured logging with correlation IDs
@Component
public class CorrelationInterceptor implements HandlerInterceptor {
private static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
private static final String CORRELATION_ID_MDC_KEY = "correlationId";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER);
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
}
MDC.put(CORRELATION_ID_MDC_KEY, correlationId);
response.setHeader(CORRELATION_ID_HEADER, correlationId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
MDC.clear();
}
}
// Business event logging
@Component
public class BusinessEventLogger {
private final Logger businessLogger = LoggerFactory.getLogger("BUSINESS_EVENTS");
private final ObjectMapper objectMapper;
@EventListener
public void logOrderCreated(OrderCreatedEvent event) {
BusinessEvent businessEvent = BusinessEvent.builder()
.eventType("ORDER_CREATED")
.aggregateId(event.orderId().toString())
.userId(event.customerId())
.timestamp(event.occurredAt())
.details(Map.of(
"itemCount", event.items().size(),
"totalAmount", event.totalAmount().toString(),
"channel", event.channel()
))
.build();
businessLogger.info("Business event: {}",
objectMapper.writeValueAsString(businessEvent));
}
@EventListener
public void logPaymentProcessed(PaymentProcessedEvent event) {
BusinessEvent businessEvent = BusinessEvent.builder()
.eventType("PAYMENT_PROCESSED")
.aggregateId(event.orderId().toString())
.userId(event.customerId())
.timestamp(event.occurredAt())
.details(Map.of(
"amount", event.amount().toString(),
"paymentMethod", event.paymentMethod(),
"transactionId", event.transactionId()
))
.build();
businessLogger.info("Business event: {}",
objectMapper.writeValueAsString(businessEvent));
}
}
Conclusion: Production-Ready Implementation
The implementation details covered in this part form the foundation for production-ready systems:
- API Design: Contract-first development with proper versioning and multiple communication protocols
- Data Layer: Multi-model architecture with CQRS, event sourcing, and distributed transaction patterns
- Security: Defense-in-depth with authentication, authorization, encryption, and comprehensive auditing
- Observability: Complete visibility through distributed tracing, metrics, health checks, and structured logging
These patterns work together to create systems that are not just functional, but observable, secure, maintainable, and scalable.
In Part 4, we'll focus on performance optimization and scalability patterns: reactive programming, caching strategies, load handling, and scaling patterns that enable your implementation to handle production loads.
Coming Next:
- Reactive programming with Spring WebFlux
- Caching strategies and cache consistency
- Performance optimization techniques
- Horizontal and vertical scaling patterns
This is Part 3 of "The Complete Guide to Modern Java Architecture." Continue with [Part 4: Performance & Scalability] for optimization strategies and scaling patterns.
Download the companion code examples and architecture templates at: GitHub Repository