This guide explains the architecture and design principles of the Firefly ECM Library.
The Firefly ECM Library implements Hexagonal Architecture (also known as Ports and Adapters pattern) with a clear separation between:
- Port Interface Library (this library): Defines business contracts and domain models
- Adapter Implementation Libraries (separate repositories): Provide concrete implementations for specific technologies
This architecture enables:
- Vendor Independence: Switch between storage providers without changing business logic
- Testability: Mock external dependencies for unit testing
- Maintainability: Clear separation of concerns
- Extensibility: Add new adapters without modifying the core library
- Modularity: Include only the adapters you need
┌─────────────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ Uses port interfaces to interact with ECM functionality │
└─────────────────────────────────────────────────────────────────┘
│
│ Depends on
▼
┌─────────────────────────────────────────────────────────────────┐
│ LIB-ECM (Port Interface Library) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ DOMAIN MODELS │ │
│ │ • Document • Folder • Permission │ │
│ │ • AuditEvent • SignatureEnvelope │ │
│ │ • DocumentVersion • FolderPermission │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ PORT INTERFACES │ │
│ │ • DocumentPort • PermissionPort │ │
│ │ • DocumentContentPort • AuditPort │ │
│ │ • SignatureEnvelopePort • FolderPort │ │
│ │ • DocumentVersionPort • SearchPort │ │
│ │ • DataExtractionPort • DocumentClassificationPort │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ ADAPTER INFRASTRUCTURE │ │
│ │ • EcmPortProvider • AdapterSelector │ │
│ │ • AdapterRegistry • Auto-Configuration │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ Implemented by (separate libraries)
▼
┌─────────────────────────────────────────────────────────────────┐
│ ADAPTER LIBRARIES │
│ (Separate Repositories) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ fireflyframework-ecm-adapter- │ │ fireflyframework-ecm-adapter- │ │ fireflyframework-ecm- │ │
│ │ s3 │ │ docusign │ │ adapter- │ │
│ │ │ │ │ │ azure-blob │ │
│ │ Implements: │ │ Implements: │ │ │ │
│ │ • DocumentPort │ │ • SignatureEnv.. │ │ Implements: │ │
│ │ • ContentPort │ │ • SignatureReq.. │ │ • DocumentP..│ │
│ └──────────────────┘ └──────────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ fireflyframework-ecm-adapter- │ │ fireflyframework-ecm-adapter- │ │ fireflyframework-ecm- │ │
│ │ adobe-sign │ │ alfresco │ │ adapter- │ │
│ │ │ │ │ │ aws-textract │ │
│ │ (Planned) │ │ (Planned) │ │ (Planned) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
The Firefly ECM implementation separates hexagonal architecture across multiple libraries:
This library contains:
- Domain Layer: Business entities and rules (Document, Folder, SignatureEnvelope, etc.)
- Port Layer: Interface contracts for external interactions
- Adapter Infrastructure: Framework for discovering and selecting adapters
Key characteristics:
- No concrete adapter implementations (except no-op fallbacks)
- No dependencies on external provider SDKs (AWS, Azure, DocuSign, etc.)
- Provides graceful degradation with no-op adapters
- Stable API that rarely changes
Each adapter library:
- Depends on fireflyframework-ecm for port interfaces and domain models
- Implements specific port interfaces for a particular technology
- Includes provider SDK dependencies (e.g., AWS SDK, DocuSign SDK)
- Registers itself via Spring Boot auto-configuration
Key characteristics:
- Can be updated independently of the core library
- Applications choose which adapters to include
- Multiple adapters can coexist (e.g., S3 + DocuSign)
- Adapter-specific configuration properties
Located in org.fireflyframework.ecm.domain.model, these represent the core business entities:
- Document: Core document entity with metadata
- DocumentVersion: Document version information
- Folder: Folder/directory structure
- FolderPermission: Folder-level permissions
- Permission: Access control permissions
- AuditEvent: Audit trail entries
- SignatureEnvelope: Container for signature workflows
- SignatureRequest: Individual signature requests
- SignatureField: Signature field definitions
Located in org.fireflyframework.ecm.port, these define the business interfaces:
- DocumentPort: Document CRUD operations
- DocumentContentPort: Binary content operations
- DocumentVersionPort: Version management
- DocumentSearchPort: Search capabilities
- FolderPort: Folder management
- FolderHierarchyPort: Hierarchical operations
- PermissionPort: Access control
- DocumentSecurityPort: Document security operations
- AuditPort: Audit logging
- SignatureEnvelopePort: Envelope management
- SignatureRequestPort: Signature requests
- SignatureValidationPort: Signature validation
- SignatureProofPort: Signature proof and evidence
- DocumentExtractionPort: OCR and text extraction
- DocumentClassificationPort: Document type classification
- DataExtractionPort: Structured data extraction
- DocumentValidationPort: Document validation
Located in org.fireflyframework.ecm.adapter, this provides the framework for adapter discovery and selection:
- AdapterRegistry: Maintains registry of available adapters
- AdapterSelector: Selects appropriate adapter based on configuration
- EcmAdapter: Base interface for all adapters
- AdapterInfo: Metadata about adapter capabilities
- AdapterProfile: Adapter configuration profiles
Adapter implementations are provided in separate libraries that depend on fireflyframework-ecm:
| Adapter | Library | Status | Implements |
|---|---|---|---|
| Amazon S3 | fireflyframework-ecm-adapter-s3 |
✅ Available | DocumentPort, DocumentContentPort |
| Azure Blob | fireflyframework-ecm-adapter-azure-blob |
✅ Available | DocumentPort, DocumentContentPort |
| MinIO | fireflyframework-ecm-adapter-minio |
🔜 Planned | DocumentPort, DocumentContentPort |
| Alfresco | fireflyframework-ecm-adapter-alfresco |
🔜 Planned | DocumentPort, FolderPort, PermissionPort |
| Adapter | Library | Status | Implements |
|---|---|---|---|
| DocuSign | fireflyframework-ecm-adapter-docusign |
✅ Available | SignatureEnvelopePort, SignatureRequestPort |
| Adobe Sign | fireflyframework-ecm-adapter-adobe-sign |
✅ Available | SignatureEnvelopePort, SignatureValidationPort |
| Logalty | fireflyframework-ecm-adapter-logalty |
🔜 Planned | SignatureEnvelopePort (eIDAS-compliant) |
| Adapter | Library | Status | Implements |
|---|---|---|---|
| AWS Textract | fireflyframework-ecm-adapter-aws-textract |
🔜 Planned | DocumentExtractionPort, DataExtractionPort |
| Azure Form Recognizer | fireflyframework-ecm-adapter-azure-form-recognizer |
🔜 Planned | DocumentExtractionPort, DataExtractionPort |
| Google Document AI | fireflyframework-ecm-adapter-google-document-ai |
🔜 Planned | DocumentExtractionPort, DataExtractionPort |
When you add an adapter library to your application:
- Dependency Resolution: Maven/Gradle includes the adapter JAR in your classpath
- Auto-Configuration: Spring Boot discovers the adapter's auto-configuration class
- Bean Registration: The adapter registers its implementation beans
- Adapter Registry: The adapter registers itself with the
AdapterRegistry
Example from an adapter library (e.g., fireflyframework-ecm-adapter-s3):
// In the adapter library (separate repository)
@EcmAdapter(
type = "s3",
description = "Amazon S3 Document Storage Adapter",
supportedFeatures = {
AdapterFeature.DOCUMENT_CRUD,
AdapterFeature.CONTENT_STORAGE,
AdapterFeature.STREAMING,
AdapterFeature.VERSIONING
},
requiredProperties = {"bucket-name", "region"},
optionalProperties = {"access-key", "secret-key", "endpoint"}
)
@Component
@ConditionalOnProperty(name = "firefly.ecm.adapter-type", havingValue = "s3")
public class S3DocumentAdapter implements DocumentPort, DocumentContentPort {
// Implementation using AWS SDK
}The AdapterSelector (provided by fireflyframework-ecm) chooses the appropriate adapter based on configuration:
- Type Matching: Matches
firefly.ecm.adapter-typewith adapter type - Feature Validation: Ensures adapter supports required features
- Configuration Validation: Validates required properties are present
- Priority Resolution: Selects highest priority adapter if multiple match
Your Application
│
├─ depends on ──> fireflyframework-ecm (port interfaces)
│
├─ depends on ──> fireflyframework-ecm-adapter-s3
│ │
│ └─ depends on ──> fireflyframework-ecm
│ └─ depends on ──> AWS SDK
│
└─ depends on ──> fireflyframework-ecm-adapter-docusign
│
└─ depends on ──> fireflyframework-ecm
└─ depends on ──> DocuSign SDK
Adapters declare their capabilities using AdapterFeature enum:
DOCUMENT_CRUD: Basic document operationsCONTENT_STORAGE: Binary content storageSTREAMING: Streaming content supportVERSIONING: Document versioningFOLDER_MANAGEMENT: Folder operationsPERMISSIONS: Access controlSEARCH: Search capabilitiesAUDIT: Audit loggingESIGNATURE_ENVELOPES: Signature envelopesESIGNATURE_REQUESTS: Signature requestsSIGNATURE_VALIDATION: Signature validation
The core library provides the configuration infrastructure:
@ConfigurationProperties(prefix = "firefly.ecm")
public class EcmProperties {
private Boolean enabled = true;
private String adapterType; // Selects which adapter to use
private ESignatureProperties esignature = new ESignatureProperties();
private Map<String, Object> adapter = new HashMap<>(); // Adapter-specific config
}Each adapter library defines its own configuration properties. For example:
S3 Adapter Configuration (in fireflyframework-ecm-adapter-s3):
firefly:
ecm:
adapter-type: s3
adapter:
s3:
bucket-name: my-bucket
region: us-east-1DocuSign Adapter Configuration (in fireflyframework-ecm-adapter-docusign):
firefly:
ecm:
esignature:
provider: docusign
adapter:
docusign:
integration-key: ${DOCUSIGN_KEY}
user-id: ${DOCUSIGN_USER}The EcmAutoConfiguration class (in fireflyframework-ecm) automatically configures the ECM system:
- Property Binding: Binds core configuration properties
- Adapter Registry Setup: Creates the adapter registry
- Port Provider Setup: Configures the port provider
- No-op Adapter Registration: Registers fallback adapters
Each adapter library provides its own auto-configuration that:
- Registers adapter beans when conditions are met
- Binds adapter-specific properties
- Validates adapter configuration
Central service that provides access to ports:
@Service
public class EcmPortProvider {
public <T> T getPort(Class<T> portType) {
return adapterSelector.getAdapter(portType);
}
public boolean isFeatureEnabled(String feature) {
return ecmProperties.getFeatures().isEnabled(feature);
}
}Maintains registry of available adapters:
@Component
public class AdapterRegistry {
public void registerAdapter(AdapterInfo adapterInfo) {
adapters.put(adapterInfo.getType(), adapterInfo);
}
public AdapterInfo getAdapter(String type) {
return adapters.get(type);
}
public Set<AdapterInfo> getAdaptersByFeature(AdapterFeature feature) {
return adapters.values().stream()
.filter(adapter -> adapter.getSupportedFeatures().contains(feature))
.collect(Collectors.toSet());
}
}The library uses Project Reactor for reactive programming:
- Non-blocking I/O: Efficient resource utilization
- Backpressure Handling: Automatic flow control
- Composable Operations: Chain operations declaratively
- Error Handling: Comprehensive error handling strategies
// Reactive document upload
public Mono<Document> uploadDocument(String name, byte[] content) {
return Mono.fromCallable(() -> createDocument(name, content))
.subscribeOn(Schedulers.boundedElastic())
.doOnSuccess(doc -> log.info("Document uploaded: {}", doc.getId()))
.doOnError(error -> log.error("Upload failed", error));
}
// Reactive content streaming
public Flux<DataBuffer> streamContent(UUID documentId) {
return contentPort.getContentStream(documentId)
.doOnSubscribe(sub -> log.info("Starting stream: {}", documentId))
.doOnComplete(() -> log.info("Stream completed: {}", documentId));
}- EcmException: Base exception for all ECM operations
- DocumentNotFoundException: Document not found
- AdapterException: Adapter-specific errors
- ConfigurationException: Configuration errors
- SecurityException: Security-related errors
- Graceful Degradation: Continue operation with reduced functionality
- Retry Logic: Automatic retry for transient failures
- Circuit Breaker: Prevent cascading failures
- Fallback Mechanisms: Alternative processing paths
- Mock Adapters: Test business logic without external dependencies
- Port Testing: Test port implementations independently
- Service Testing: Test service layer with mocked ports
- Adapter Testing: Test adapters with real external systems
- End-to-End Testing: Test complete workflows
- Configuration Testing: Test different configuration scenarios
- Adapter-level Security: Each adapter handles its own authentication
- Permission System: Fine-grained access control
- Audit Logging: Complete audit trail for compliance
- Encryption at Rest: Adapter-specific encryption
- Encryption in Transit: HTTPS/TLS for all communications
- Data Masking: Sensitive data protection in logs
- Metadata Caching: Cache document metadata
- Content Caching: Cache frequently accessed content
- Configuration Caching: Cache adapter configurations
- Connection Pooling: Efficient connection reuse
- Timeout Configuration: Prevent hanging operations
- Retry Logic: Handle transient failures
- Operation Metrics: Document operations per second
- Performance Metrics: Response times and throughput
- Error Metrics: Error rates and types
- Structured Logging: JSON-formatted logs
- Correlation IDs: Track operations across components
- Audit Logging: Compliance and security auditing
Implement custom adapters by:
- Implementing required port interfaces
- Adding
@EcmAdapterannotation - Registering as Spring component
- Providing configuration properties
Add custom features by:
- Defining new port interfaces
- Implementing in adapters
- Adding feature flags
- Updating configuration
- Implement Required Interfaces: Implement all required ports
- Handle Errors Gracefully: Provide meaningful error messages
- Support Configuration: Use configuration properties
- Add Comprehensive Tests: Unit and integration tests
- Use Port Interfaces: Don't depend on adapter implementations
- Handle Reactive Streams: Use proper reactive patterns
- Configure Properly: Validate configuration on startup
- Monitor Operations: Add metrics and logging