Service Provider Interface (SPI)
This guide explains how TrustWeave uses the Java Service Provider Interface (SPI) for auto-discovery of plugins and adapters.
Overview
TrustWeave uses SPI to automatically discover and load plugins at runtime without requiring explicit registration code. This enables:
- Auto-discovery – plugins are automatically found on the classpath
- Loose coupling – plugins can be added without modifying application code
- Modularity – plugins are discovered independently of the main application
How SPI Works in TrustWeave
Provider Interfaces
TrustWeave defines several provider interfaces:
DidMethodProvider– discovers DID method implementationsKeyManagementServiceProvider– discovers KMS implementationsBlockchainAnchorClientProvider– discovers blockchain adapter implementationsCredentialServiceProvider– discovers credential service implementations
Service Discovery
SPI uses service files in META-INF/services/ to discover providers:
1
2
3
META-INF/services/com.trustweave.did.spi.DidMethodProvider
META-INF/services/com.trustweave.kms.spi.KeyManagementServiceProvider
META-INF/services/com.trustweave.anchor.spi.BlockchainAnchorClientProvider
Each file contains the fully qualified class name of the provider implementation.
Using SPI
Discovering Providers
1
2
3
4
5
6
7
8
9
10
11
import com.trustweave.did.spi.DidMethodProvider
import java.util.ServiceLoader
// Discover DID method providers
val providers = ServiceLoader.load(DidMethodProvider::class.java)
// Iterate through discovered providers
providers.forEach { provider ->
println("Provider: ${provider.name}")
println("Supported methods: ${provider.supportedMethods}")
}
What this does: Uses ServiceLoader to discover all DidMethodProvider implementations on the classpath.
Outcome: Automatically finds all DID method providers without explicit registration.
Creating Providers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.trustweave.did.spi.DidMethodProvider
import com.trustweave.did.*
class MyDidMethodProvider : DidMethodProvider {
override val name: String = "my-did-method"
override val supportedMethods: List<String> = listOf("mydid")
override fun create(
method: String,
options: DidCreationOptions
): DidMethod? {
if (method != "mydid") return null
return MyDidMethod(options)
}
}
What this does: Implements DidMethodProvider to provide a custom DID method.
Outcome: Enables auto-discovery of your custom DID method.
Registering Service Files
Create a service file at:
1
src/main/resources/META-INF/services/com.trustweave.did.spi.DidMethodProvider
With content:
1
com.example.MyDidMethodProvider
What this does: Registers your provider for auto-discovery.
Outcome: Your provider is automatically discovered when the module is on the classpath.
SPI in TrustWeave Modules
DID Method Providers
1
2
// did/plugins/key/src/main/resources/META-INF/services/...
com.trustweave.did.key.KeyDidMethodProvider
KMS Providers
1
2
// kms/plugins/aws/src/main/resources/META-INF/services/...
com.trustweave.awskms.AwsKeyManagementServiceProvider
Blockchain Adapter Providers
1
2
// chains/plugins/algorand/src/main/resources/META-INF/services/...
com.trustweave.algorand.AlgorandBlockchainAnchorClientProvider
Benefits of SPI
Modularity
Plugins can be added or removed without modifying application code:
1
2
3
4
// Add chains/plugins/algorand dependency to enable Algorand adapter
dependencies {
implementation("com.trustweave:chains/plugins/algorand:1.0.0-SNAPSHOT")
}
Loose Coupling
Applications don’t need to know about specific implementations:
1
2
3
// Application code works with any DID method provider
val providers = ServiceLoader.load(DidMethodProvider::class.java)
val provider = providers.find { it.supportedMethods.contains("key") }
Testability
SPI enables easy mocking and testing:
1
2
3
// Test can provide mock providers
val mockProvider = MockDidMethodProvider()
// Test implementation
Best Practices
Provider Naming
- Use descriptive, unique names
- Follow naming conventions (lowercase, hyphen-separated)
- Include module/plugin identifier
Error Handling
1
2
3
4
5
6
7
8
9
10
11
val providers = ServiceLoader.load(DidMethodProvider::class.java)
providers.forEach { provider ->
try {
val method = provider.create("key", options)
// Use method
} catch (e: Exception) {
// Handle provider creation errors
println("Failed to create method from provider ${provider.name}: ${e.message}")
}
}
Provider Validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override fun create(
method: String,
options: DidCreationOptions
): DidMethod? {
// Validate method name
if (!supportedMethods.contains(method)) {
return null
}
// Validate required options
val requiredOption = options.additionalProperties["requiredOption"] as? String
?: return null
return MyDidMethod(options)
}
Troubleshooting
Provider Not Found
Problem: Provider not discovered
Solution:
- Verify service file exists in
META-INF/services/ - Check service file contains correct class name
- Ensure provider class is on classpath
- Check for typos in service file
Multiple Providers
Problem: Multiple providers for same method
Solution:
- TrustWeave uses the first provider found
- Use explicit provider selection if needed
- Order providers via dependency order if critical
Next Steps
- Review Creating Plugins for plugin implementation
- See Plugin Lifecycle for lifecycle management
- SPI interfaces are included in
trustweave-common. See Core Modules for details