Back to blog
Dec 26, 2024
5 min read

Configuration driven REST framework - 3.Examples (Configuration driven REST framework)

Comprehensive Guide to Strategy Pattern Implementation with Spring Framework

Additional Framework Implementation Examples

3. Notification Service

NotificationRequest.java

@Data
@Builder
public class NotificationRequest {
@NotNull(message = "Recipient is required")
private NotificationRecipient recipient;
@NotBlank(message = "Template ID is required")
private String templateId;
@Valid
private Map<String, Object> templateVariables;
@NotNull(message = "Channel is required")
private NotificationChannel channel;
@Future(message = "Scheduled time must be in the future")
private LocalDateTime scheduledTime;
private NotificationPriority priority = NotificationPriority.NORMAL;
}
@Data
public class NotificationRecipient {
@NotBlank(message = "User ID is required")
private String userId;
@Email(message = "Valid email is required")
private String email;
@Pattern(regexp = "^\\+[1-9]\\d{1,14}$", message = "Phone number must be in E.164 format")
private String phoneNumber;
}

NotificationProcessor.java

@Component("notificationProcessor")
@Slf4j
public class NotificationProcessor implements BusinessLogic<NotificationRequest, NotificationResult> {
private final TemplateEngine templateEngine;
private final NotificationChannelFactory channelFactory;
private final NotificationRepository repository;
@Override
@Transactional
public NotificationResult execute(NotificationRequest request) {
// Process template
String content = templateEngine.process(
request.getTemplateId(),
request.getTemplateVariables()
);
// Create notification
Notification notification = Notification.builder()
.recipient(request.getRecipient())
.content(content)
.channel(request.getChannel())
.scheduledTime(request.getScheduledTime())
.priority(request.getPriority())
.status(NotificationStatus.PENDING)
.build();
// Save notification
notification = repository.save(notification);
// If immediate delivery is required
if (request.getScheduledTime() == null ||
request.getScheduledTime().isBefore(LocalDateTime.now())) {
return sendNotification(notification);
}
return NotificationResult.builder()
.notificationId(notification.getId())
.status(NotificationStatus.SCHEDULED)
.scheduledTime(request.getScheduledTime())
.build();
}
private NotificationResult sendNotification(Notification notification) {
try {
NotificationChannel channel = channelFactory.getChannel(notification.getChannel());
DeliveryResult result = channel.send(notification);
notification.setStatus(result.isSuccess() ?
NotificationStatus.DELIVERED : NotificationStatus.FAILED);
notification.setDeliveryAttempts(notification.getDeliveryAttempts() + 1);
notification.setLastAttemptTime(LocalDateTime.now());
notification.setErrorMessage(result.getErrorMessage());
repository.save(notification);
return NotificationResult.builder()
.notificationId(notification.getId())
.status(notification.getStatus())
.deliveryTime(notification.getLastAttemptTime())
.errorMessage(result.getErrorMessage())
.build();
} catch (Exception e) {
log.error("Failed to send notification: {}", notification.getId(), e);
throw new NotificationDeliveryException("Failed to send notification", e);
}
}
}

4. Report Generation Service

ReportRequest.java

@Data
@Builder
public class ReportRequest {
@NotBlank(message = "Report type is required")
private String reportType;
@Valid
private DateRange dateRange;
private List<String> metrics;
private List<String> dimensions;
@Valid
private ReportFormat format;
private Map<String, Object> filters;
@Max(value = 100000, message = "Maximum rows exceeded")
private Integer maxRows;
}
@Data
public class DateRange {
@NotNull(message = "Start date is required")
@Past(message = "Start date must be in the past")
private LocalDate startDate;
@NotNull(message = "End date is required")
@Past(message = "End date must be in the past")
private LocalDate endDate;
@AssertTrue(message = "Date range cannot exceed 1 year")
public boolean isValidDateRange() {
return startDate != null && endDate != null &&
!startDate.plusYears(1).isBefore(endDate);
}
}

ReportGenerator.java

@Component("reportGenerator")
public class ReportGenerator implements BusinessLogic<ReportRequest, ReportResult> {
private final ReportQueryBuilder queryBuilder;
private final DataSourceRouter dataSource;
private final ReportFormatFactory formatFactory;
private final ReportCache reportCache;
@Override
public ReportResult execute(ReportRequest request) {
// Check cache first
String cacheKey = generateCacheKey(request);
ReportResult cachedResult = reportCache.get(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
// Build query
Query query = queryBuilder
.setType(request.getReportType())
.setDateRange(request.getDateRange())
.setMetrics(request.getMetrics())
.setDimensions(request.getDimensions())
.setFilters(request.getFilters())
.setMaxRows(request.getMaxRows())
.build();
// Execute query
DataSet dataSet = dataSource.executeQuery(query);
// Format results
ReportFormatter formatter = formatFactory.getFormatter(request.getFormat());
byte[] formattedReport = formatter.format(dataSet);
// Create result
ReportResult result = ReportResult.builder()
.reportId(UUID.randomUUID().toString())
.format(request.getFormat())
.data(formattedReport)
.totalRows(dataSet.getRowCount())
.generatedAt(LocalDateTime.now())
.build();
// Cache result
reportCache.put(cacheKey, result);
return result;
}
private String generateCacheKey(ReportRequest request) {
return DigestUtils.md5Hex(JsonUtils.toJson(request));
}
}

5. Document Processing Service

DocumentRequest.java

@Data
@Builder
public class DocumentRequest {
@NotNull(message = "Document type is required")
private DocumentType documentType;
@NotNull(message = "Input document is required")
private MultipartFile inputDocument;
@Valid
private List<ProcessingOperation> operations;
private ProcessingPriority priority;
@Size(max = 10, message = "Maximum 10 callbacks allowed")
private List<String> callbackUrls;
}
@Data
public class ProcessingOperation {
@NotNull(message = "Operation type is required")
private OperationType type;
private Map<String, Object> parameters;
@AssertTrue(message = "Invalid operation parameters")
public boolean isValidOperation() {
return OperationValidator.validate(type, parameters);
}
}

DocumentProcessor.java

@Component("documentProcessor")
@Slf4j
public class DocumentProcessor implements BusinessLogic<DocumentRequest, DocumentResult> {
private final StorageService storageService;
private final ProcessingOperationFactory operationFactory;
private final CallbackService callbackService;
@Override
public DocumentResult execute(DocumentRequest request) {
// Store original document
String documentId = storageService.store(request.getInputDocument());
// Create processing job
ProcessingJob job = ProcessingJob.builder()
.documentId(documentId)
.operations(request.getOperations())
.priority(request.getPriority())
.status(ProcessingStatus.PENDING)
.callbackUrls(request.getCallbackUrls())
.build();
// Process document
if (request.getPriority() == ProcessingPriority.HIGH) {
return processDocumentSync(job);
} else {
return processDocumentAsync(job);
}
}
private DocumentResult processDocumentSync(ProcessingJob job) {
try {
Document document = storageService.retrieve(job.getDocumentId());
for (ProcessingOperation operation : job.getOperations()) {
DocumentProcessor processor = operationFactory.getProcessor(operation.getType());
document = processor.process(document, operation.getParameters());
}
// Store processed document
String processedDocumentId = storageService.store(document);
return DocumentResult.builder()
.jobId(job.getId())
.status(ProcessingStatus.COMPLETED)
.outputDocumentId(processedDocumentId)
.build();
} catch (Exception e) {
log.error("Document processing failed: {}", job.getId(), e);
throw new DocumentProcessingException("Processing failed", e);
}
}
private DocumentResult processDocumentAsync(ProcessingJob job) {
// Submit to processing queue
processingQueue.submit(job);
return DocumentResult.builder()
.jobId(job.getId())
.status(ProcessingStatus.PROCESSING)
.build();
}
}

These examples demonstrate:

  1. Complex validation rules
  2. Async vs sync processing
  3. Caching strategies
  4. Multiple processing steps
  5. Error handling
  6. Callback mechanisms
  7. File handling
  8. Queue-based processing
  9. Different output formats
  10. Service integration

These examples can be easily extended and customized to suit specific business requirements. The framework provides a solid foundation for building scalable and maintainable REST services.