The WFM Archive service layer provides the core business logic through a collection of well-defined service interfaces and their implementations.
- Archive Service - Document archiving and lifecycle management
- MongoDB Service - Document storage in MongoDB
- S3 Service - File storage in Amazon S3
- Keycloak Service - User authentication and management
- Workflow Service - Business process automation
- Integration Services - External system integrations
WFM Archive follows CUBA Platform's service pattern with interfaces in the global module and implementations in the core module.
graph TB
subgraph "Global Module - Service Interfaces"
AS[ArchiveService]
MS[MongoService]
S3S[S3Service]
KS[KeycloakService]
WS[WorkflowService]
IS[IntegrationService]
end
subgraph "Core Module - Service Implementations"
ASB[ArchiveServiceBean]
MSB[MongoServiceBean]
S3SB[S3ServiceBean]
KSB[KeycloakServiceBean]
WSB[WorkflowServiceBean]
ISB[IntegrationServiceBean]
end
AS --> ASB
MS --> MSB
S3S --> S3SB
KS --> KSB
WS --> WSB
IS --> ISB
ASB --> MSB
ASB --> S3SB
ASB --> KSB
WSB --> ASB
- ArchiveService - Primary document management operations
- MongoService - Document storage and retrieval
- AmazonS3Service - File storage and migration
- KeycloakService - SSO and user management
- SqlDataService - External database connections
- RestMappingService - API data transformation
- ISeriesIntegrationService - IBM i-Series connectivity
- WorkflowService - BPMN workflow execution
- ProcessRuntimeService - Process instance management
- ActivityInstanceDataService - Workflow tracking
- DocumentTypeService - Document type management
- RegistrationService - System registration
- JsonImportService - Data import functionality
- RestCachedService - Caching and validation
- CustomControllerService - Custom business logic
All services follow the CUBA Platform pattern:
// modules/global/src/dk/bpas/wfmarchive/service/ArchiveService.java
public interface ArchiveService {
String NAME = "wfmarchive_ArchiveService";
Document archiveDocument(DocumentDto document);
Document retrieveDocument(String documentId);
void deleteDocument(String documentId);
}// modules/core/src/dk/bpas/wfmarchive/service/ArchiveServiceBean.java
@Service(ArchiveService.NAME)
public class ArchiveServiceBean implements ArchiveService {
@Inject
private MongoService mongoService;
@Inject
private AmazonS3Service s3Service;
@Override
@Transactional
public Document archiveDocument(DocumentDto document) {
// Implementation
}
}// Usage in other services or controllers
public class DocumentController {
@Inject
private ArchiveService archiveService;
public void processDocument() {
archiveService.archiveDocument(dto);
}
}graph TD
REST[REST Controllers] --> ARCH[ArchiveService]
ARCH --> MONGO[MongoService]
ARCH --> S3[S3Service]
ARCH --> KC[KeycloakService]
WF[WorkflowService] --> ARCH
WF --> PROC[ProcessRuntimeService]
INTEG[IntegrationService] --> SQL[SqlDataService]
INTEG --> ISERIES[ISeriesService]
MAPPING[RestMappingService] --> ARCH
MAPPING --> CACHE[RestCachedService]
Services use Spring's @Transactional annotation for database operations:
@Service(ArchiveService.NAME)
public class ArchiveServiceBean implements ArchiveService {
@Override
@Transactional
public Document archiveDocument(DocumentDto document) {
// All database operations in single transaction
validateDocument(document);
Document saved = saveToDatabase(document);
auditLog(saved);
return saved;
}
@Override
@Transactional(readOnly = true)
public Document retrieveDocument(String id) {
// Read-only transaction
return findDocument(id);
}
}Services use consistent error handling patterns:
public class ArchiveServiceBean implements ArchiveService {
@Override
public Document archiveDocument(DocumentDto document) {
try {
validateDocument(document);
return processDocument(document);
} catch (ValidationException e) {
log.error("Document validation failed: {}", e.getMessage());
throw new ServiceException("Invalid document", e);
} catch (StorageException e) {
log.error("Storage error: {}", e.getMessage());
throw new ServiceException("Storage unavailable", e);
}
}
}@Service(RestCachedService.NAME)
public class RestCachedServiceBean implements RestCachedService {
@Cacheable("documentTypes")
public DocumentType getDocumentType(String code) {
return dataManager.load(DocumentType.class)
.query("select dt from DocumentType dt where dt.code = :code")
.parameter("code", code)
.one();
}
}@Service(WorkflowService.NAME)
public class WorkflowServiceBean implements WorkflowService {
@Async
public CompletableFuture<ProcessInstance> startProcessAsync(String processKey, Map<String, Object> variables) {
ProcessInstance instance = runtimeService.startProcessInstanceByKey(processKey, variables);
return CompletableFuture.completedFuture(instance);
}
}@Service(SqlDataService.NAME)
public class SqlDataServiceBean implements SqlDataService {
@Value("${external.db.pool.size:10}")
private int poolSize;
private HikariDataSource createDataSource(Database database) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(database.getConnectionString());
config.setMaximumPoolSize(poolSize);
config.setLeakDetectionThreshold(120000);
return new HikariDataSource(config);
}
}Services integrate with CUBA Platform's security:
@Service(ArchiveService.NAME)
public class ArchiveServiceBean implements ArchiveService {
@Inject
private UserSessionSource userSessionSource;
@Override
public Document archiveDocument(DocumentDto document) {
UserSession userSession = userSessionSource.getUserSession();
// Check permissions
if (!userSession.isPermitted("document.archive")) {
throw new AccessDeniedException("Archive permission required");
}
// Set audit fields
document.setCreatedBy(userSession.getUser().getLogin());
document.setCreatedDate(new Date());
return processDocument(document);
}
}@ExtendWith(MockitoExtension.class)
class ArchiveServiceTest {
@Mock
private MongoService mongoService;
@Mock
private S3Service s3Service;
@InjectMocks
private ArchiveServiceBean archiveService;
@Test
void testArchiveDocument() {
// Given
DocumentDto dto = new DocumentDto();
when(mongoService.store(any())).thenReturn("doc-123");
// When
Document result = archiveService.archiveDocument(dto);
// Then
assertThat(result.getId()).isEqualTo("doc-123");
verify(mongoService).store(any());
}
}@SpringBootTest
@Transactional
class ArchiveServiceIntegrationTest {
@Autowired
private ArchiveService archiveService;
@Test
void testArchiveDocumentIntegration() {
// Test with real database and services
DocumentDto dto = createTestDocument();
Document result = archiveService.archiveDocument(dto);
assertThat(result).isNotNull();
}
}- Interface Segregation - Keep service interfaces focused and cohesive
- Dependency Injection - Use
@Injectfor service dependencies - Transaction Boundaries - Define clear transaction scopes
- Error Handling - Use consistent exception patterns
- Logging - Include comprehensive logging for troubleshooting
- Testing - Write both unit and integration tests
- Documentation - Document service contracts and behaviors
Explore the detailed documentation for each service:
- Start with Archive Service for core document operations
- Review MongoDB Service for storage details
- Check Integration Services for external connections