Back to blog
Dec 25, 2024
5 min read

Configuration driven REST framework - 2.Implmentation (Configuration driven REST framework)

Comprehensive Guide to Strategy Pattern Implementation with Spring Framework

Configuration-Driven REST Framework Documentation

Table of Contents

  1. Core Components
  2. Configuration
  3. Transformers
  4. Business Logic
  5. Examples
  6. Testing Guide

Core Components

EndpointConfig.java

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EndpointConfig {
@NotBlank(message = "Path is required")
@Pattern(regexp = "^/.*", message = "Path must start with /")
private String path;
@NotNull(message = "HTTP method is required")
@Pattern(regexp = "^(GET|POST|PUT|DELETE|PATCH)$", message = "Invalid HTTP method")
private String httpMethod;
@NotEmpty(message = "At least one access right is required")
private List<String> requiredRights;
@NotBlank(message = "Input transformer is required")
private String inputTransformer;
@NotBlank(message = "Business logic bean is required")
private String businessLogicBean;
@NotBlank(message = "Output transformer is required")
private String outputTransformer;
@Valid
private ValidationRules validationRules;
}

This is the core configuration class that defines the structure of each endpoint. The annotations provide validation to ensure all required fields are properly set.

ValidationRules.java

@Data
public class ValidationRules {
private RateLimitRule rateLimit;
private Long maxRequestSizeBytes;
private List<String> allowedContentTypes;
private Map<String, String> customValidations;
}

Defines validation rules that can be applied to any endpoint through configuration.

Examples

1. Order Processing System

OrderController.java

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final DynamicEndpointHandler endpointHandler;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
return endpointHandler.handle(request);
}
}

OrderRequest.java

@Data
@Builder
public class OrderRequest {
@NotEmpty(message = "Order items are required")
private List<OrderItem> items;
@Valid
private ShippingAddress shippingAddress;
@NotNull(message = "Payment method is required")
private PaymentMethod paymentMethod;
}
@Data
public class OrderItem {
@NotNull(message = "Product ID is required")
private Long productId;
@Min(value = 1, message = "Quantity must be at least 1")
private int quantity;
}

OrderProcessingStrategy.java

@Component("orderProcessingStrategy")
@Transactional
public class OrderProcessingStrategy implements BusinessLogic<OrderRequest, OrderResponse> {
private final ProductRepository productRepository;
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
@Override
public OrderResponse execute(OrderRequest request) {
// Validate inventory
validateInventory(request.getItems());
// Calculate total
BigDecimal total = calculateTotal(request.getItems());
// Process payment
PaymentResult payment = processPayment(request.getPaymentMethod(), total);
// Create order
Order order = createOrder(request, total, payment);
// Update inventory
updateInventory(request.getItems());
return mapToResponse(order);
}
}

2. Real-time Analytics Processing

AnalyticsEvent.java

@Data
@Builder
public class AnalyticsEvent {
@NotBlank(message = "Event type is required")
private String eventType;
@NotNull(message = "Timestamp is required")
private LocalDateTime timestamp;
@Valid
private Map<String, Object> properties;
@NotNull(message = "User ID is required")
private String userId;
}

AnalyticsProcessor.java

@Component("analyticsProcessor")
public class AnalyticsProcessor implements BusinessLogic<AnalyticsEvent, AnalyticsResult> {
private final KafkaTemplate<String, AnalyticsEvent> kafkaTemplate;
private final RedisTemplate<String, String> redisTemplate;
@Override
public AnalyticsResult execute(AnalyticsEvent event) {
// Process real-time metrics
updateRealTimeMetrics(event);
// Send to Kafka for batch processing
sendToKafka(event);
// Return immediate insights
return generateQuickInsights(event);
}
}

Testing Guide

1. Unit Testing

Testing Transformers

@ExtendWith(MockitoExtension.class)
class OrderRequestTransformerTest {
@InjectMocks
private OrderRequestTransformer transformer;
@Test
void shouldTransformValidRequest() {
// Given
String jsonRequest = """
{
"items": [
{"productId": 1, "quantity": 2}
],
"shippingAddress": {
"street": "123 Main St",
"city": "Boston",
"zipCode": "02108"
},
"paymentMethod": "CREDIT_CARD"
}
""";
// When
OrderRequest result = transformer.transform(jsonRequest);
// Then
assertNotNull(result);
assertEquals(1, result.getItems().size());
assertEquals(2, result.getItems().get(0).getQuantity());
}
}

Testing Business Logic

@ExtendWith(MockitoExtension.class)
class OrderProcessingStrategyTest {
@Mock
private ProductRepository productRepository;
@Mock
private InventoryService inventoryService;
@InjectMocks
private OrderProcessingStrategy strategy;
@Test
void shouldProcessValidOrder() {
// Given
OrderRequest request = createSampleOrder();
when(productRepository.findById(anyLong()))
.thenReturn(Optional.of(createSampleProduct()));
// When
OrderResponse response = strategy.execute(request);
// Then
assertNotNull(response);
verify(inventoryService).updateInventory(any());
}
}

2. Integration Testing

Configuration Testing

@SpringBootTest
class EndpointConfigurationTest {
@Autowired
private ConfigurationLoader configLoader;
@Test
void shouldLoadValidConfiguration() {
// Given
String configPath = "test-config.json";
// When
List<EndpointConfig> configs = configLoader.loadConfiguration(configPath);
// Then
assertFalse(configs.isEmpty());
configs.forEach(this::validateEndpointConfig);
}
}

End-to-End Testing

@SpringBootTest
@AutoConfigureMockMvc
class OrderEndpointTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldCreateOrder() throws Exception {
// Given
String requestBody = createSampleOrderJson();
// When/Then
mockMvc.perform(post("/api/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isOk())
.andExpect(jsonPath("$.orderId").exists())
.andExpect(jsonPath("$.status").value("CREATED"));
}
}

3. Performance Testing

Load Testing with JMeter

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="REST Framework Load Test">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">false</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Order Creation Test">
<elementProp name="ThreadGroup.main_controller" elementType="LoopController">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">50</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<longProp name="ThreadGroup.start_time">1373789594000</longProp>
<longProp name="ThreadGroup.end_time">1373789594000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
</hashTree>
</hashTree>
</jmeterTestPlan>

Best Practices

  1. Always validate input at both the transformer and business logic levels
  2. Use proper exception handling with custom exceptions
  3. Implement proper logging at all levels
  4. Use proper transaction management for database operations
  5. Implement proper security measures
  6. Use caching where appropriate
  7. Follow SOLID principles
  8. Write comprehensive tests
  9. Document all public APIs
  10. Use proper version control and branching strategies