platform-java supports declarative application dependencies, allowing applications to specify which services they require from other applications. The platform automatically:
- Validates dependencies at deploy time
- Detects circular dependencies
- Computes ordered startup sequences
- Manages service lifecycle based on dependency graph
import org.flossware.platform-java.api.ApplicationDependency;
import org.flossware.platform-java.api.ApplicationDependency.DependencyType;
ApplicationDescriptor descriptor = ApplicationDescriptor.builder()
.applicationId("order-service")
.mainClass("com.example.OrderService")
.addClasspathEntry("file:///path/to/order-service.jar")
// Required dependency - deployment fails if not available
.addDependency(new ApplicationDependency(
"com.example.DatabaseService",
DependencyType.REQUIRED,
"1.0.0"
))
// Optional dependency - deployment succeeds even if not available
.addDependency(new ApplicationDependency(
"com.example.CacheService",
DependencyType.OPTIONAL,
"latest"
))
.build();applicationId: order-service
mainClass: com.example.OrderService
classpathEntries:
- file:///path/to/order-service.jar
dependencies:
- serviceInterface: com.example.DatabaseService
type: REQUIRED
version: "1.0.0"
- serviceInterface: com.example.CacheService
type: OPTIONAL
version: latest{
"applicationId": "order-service",
"mainClass": "com.example.OrderService",
"classpathEntries": [
"file:///path/to/order-service.jar"
],
"dependencies": [
{
"serviceInterface": "com.example.DatabaseService",
"type": "REQUIRED",
"version": "1.0.0"
},
{
"serviceInterface": "com.example.CacheService",
"type": "OPTIONAL",
"version": "latest"
}
]
}- Deployment fails if service is not registered in ServiceRegistry
- Application cannot start without this service
- Use for critical dependencies (database, authentication, etc.)
.addDependency(new ApplicationDependency(
"com.example.DatabaseService",
DependencyType.REQUIRED,
"1.0.0"
))- Deployment succeeds even if service is not available
- Application must handle null/absent service gracefully
- Use for non-critical dependencies (caching, metrics, etc.)
.addDependency(new ApplicationDependency(
"com.example.CacheService",
DependencyType.OPTIONAL,
"latest"
))Use semantic versioning (semver) format: MAJOR.MINOR.PATCH
"1.0.0" // Exact version 1.0.0
"1.2" // Version 1.2.x (any patch)
"2" // Version 2.x.x (any minor/patch)Use "latest" to accept any version:
.addDependency(new ApplicationDependency(
"com.example.CacheService",
DependencyType.OPTIONAL,
"latest" // Accept any version
))When you deploy an application, platform-java:
- Validates all REQUIRED dependencies exist in ServiceRegistry
- Checks version compatibility (if specified)
- Detects circular dependencies using graph algorithms
- Computes startup order using topological sort
If validation fails, deployment is rejected with detailed error message.
$ java -jar platform-java-launcher.jar deploy --yaml order-service.yaml
INFO Validating dependencies for order-service...
INFO ✓ Found com.example.DatabaseService v1.0.0 (REQUIRED)
INFO ✓ Found com.example.CacheService v2.1.0 (OPTIONAL)
INFO ✓ No circular dependencies detected
INFO Application deployed successfully$ java -jar platform-java-launcher.jar deploy --yaml order-service.yaml
ERROR Dependency validation failed for order-service
ERROR ✗ Required dependency not found: com.example.DatabaseService
ERROR Deployment abortedplatform-java automatically starts applications in dependency order.
database-service (no dependencies)
↓
cache-service (depends on database-service)
↓
order-service (depends on cache-service and database-service)
$ java -jar platform-java-launcher.jar start --app-id order-service
INFO Computing startup order...
INFO Startup sequence: [database-service, cache-service, order-service]
INFO Starting database-service...
INFO database-service is now RUNNING
INFO Starting cache-service...
INFO cache-service is now RUNNING
INFO Starting order-service...
INFO order-service is now RUNNINGplatform-java detects circular dependencies at deploy time:
app-a depends on app-b
app-b depends on app-c
app-c depends on app-a ← circular!
ERROR Circular dependency detected: app-a → app-b → app-c → app-a
ERROR Deployment abortedplatform-java uses Depth-First Search (DFS) with recursion stack to detect cycles in O(V + E) time.
Dependencies are resolved against the ServiceRegistry:
public class DatabaseService implements Application {
@Override
public void start(ApplicationContext context) throws Exception {
// Register service implementation
context.getServiceRegistry().ifPresent(registry -> {
registry.registerService(
DatabaseService.class,
this,
"1.0.0" // Version
);
});
}
@Override
public void stop() throws Exception {
// Unregister on shutdown
context.getServiceRegistry().ifPresent(registry -> {
registry.unregisterService(DatabaseService.class, this);
});
}
}public class OrderService implements Application {
private DatabaseService database;
private CacheService cache;
@Override
public void start(ApplicationContext context) throws Exception {
context.getServiceRegistry().ifPresent(registry -> {
// Required dependency - throws if not found
database = registry.getService(DatabaseService.class)
.orElseThrow(() -> new IllegalStateException("Database service not available"));
// Optional dependency - handle absence gracefully
cache = registry.getService(CacheService.class)
.orElse(new NoOpCacheService());
});
}
}Applications can implement HealthCheck to report service health:
package com.example;
import org.flossware.platform-java.api.Application;
import org.flossware.platform-java.api.HealthCheck;
import org.flossware.platform-java.api.ApplicationContext;
public class DatabaseService implements Application, HealthCheck {
private Connection connection;
@Override
public void start(ApplicationContext context) throws Exception {
connection = DriverManager.getConnection("jdbc:...");
// Register as service
context.getServiceRegistry().ifPresent(registry -> {
registry.registerService(DatabaseService.class, this, "1.0.0");
});
}
@Override
public boolean isHealthy() {
try {
return connection != null && !connection.isClosed();
} catch (SQLException e) {
return false;
}
}
@Override
public String getHealthMessage() {
if (isHealthy()) {
return "Database connection is active";
} else {
return "Database connection is down";
}
}
}Health checks can be polled periodically to notify dependent applications:
// Future feature: automatic health monitoring
DependencyResolver resolver = new DependencyResolver(serviceRegistry);
resolver.startHealthMonitoring(Duration.ofSeconds(30)); // Check every 30s
resolver.addHealthListener((serviceInterface, healthy, message) -> {
if (!healthy) {
logger.warn("Service {} is unhealthy: {}", serviceInterface, message);
// Notify dependent applications
}
});// ✅ Good: database is critical
.addDependency(new ApplicationDependency(
"com.example.DatabaseService",
DependencyType.REQUIRED,
"1.0.0"
))
// ❌ Bad: database marked as optional
.addDependency(new ApplicationDependency(
"com.example.DatabaseService",
DependencyType.OPTIONAL, // App will fail at runtime!
"1.0.0"
))// ✅ Good: provide fallback for optional service
cache = registry.getService(CacheService.class)
.orElse(new NoOpCacheService());
// ❌ Bad: throw exception for optional dependency
cache = registry.getService(CacheService.class)
.orElseThrow(); // Defeats purpose of OPTIONAL!// ✅ Good: pin to specific version for production
.addDependency(new ApplicationDependency(
"com.example.DatabaseService",
DependencyType.REQUIRED,
"1.2.0" // Exact version
))
// ⚠️ Acceptable: use "latest" for development
.addDependency(new ApplicationDependency(
"com.example.CacheService",
DependencyType.OPTIONAL,
"latest" // Any version OK
))// ❌ Bad: circular dependency
// service-a depends on service-b
// service-b depends on service-a
// ✅ Good: extract shared logic to common service
// service-a depends on common-service
// service-b depends on common-service// ✅ Good: depend on interface
.addDependency(new ApplicationDependency(
"com.example.DatabaseService", // Interface
DependencyType.REQUIRED,
"1.0.0"
))
// Multiple implementations can be registered
registry.registerService(DatabaseService.class, mysqlImpl, "1.0.0");
registry.registerService(DatabaseService.class, postgresImpl, "1.0.0");# database-service.yaml
applicationId: database-service
mainClass: com.example.DatabaseService
classpathEntries:
- file:///services/database-service.jar
dependencies: [] # No dependencies# cache-service.yaml
applicationId: cache-service
mainClass: com.example.CacheService
classpathEntries:
- file:///services/cache-service.jar
dependencies:
- serviceInterface: com.example.DatabaseService
type: REQUIRED
version: "1.0.0"# order-service.yaml
applicationId: order-service
mainClass: com.example.OrderService
classpathEntries:
- file:///services/order-service.jar
dependencies:
- serviceInterface: com.example.DatabaseService
type: REQUIRED
version: "1.0.0"
- serviceInterface: com.example.CacheService
type: REQUIRED
version: "1.0.0"# Deploy all services (order doesn't matter)
$ platform-java-launcher.jar deploy --yaml database-service.yaml
$ platform-java-launcher.jar deploy --yaml cache-service.yaml
$ platform-java-launcher.jar deploy --yaml order-service.yaml
# Start top-level service (dependencies auto-start)
$ platform-java-launcher.jar start --app-id order-service
# Output:
# INFO Computing startup order...
# INFO Startup sequence: [database-service, cache-service, order-service]
# INFO Starting database-service...
# INFO Starting cache-service...
# INFO Starting order-service...
# INFO All services started successfullyCause: Service interface not registered in ServiceRegistry.
Solution:
- Verify provider application is deployed:
GET /api/applications - Check provider calls
registry.registerService()instart() - Ensure provider is started before dependent application
Cause: Applications have circular dependency chain.
Solution:
- Review dependency graph
- Refactor to extract shared logic into separate service
- Consider using MessageBus for loose coupling instead of direct dependencies
Cause: Registered service version doesn't match required version.
Solution:
- Update dependency to accept broader version:
"1"instead of"1.0.0" - Deploy correct version of service
- Use
"latest"for version-agnostic dependencies (not recommended for production)
public class ApplicationDependency {
public ApplicationDependency(String serviceInterface, DependencyType type, String version);
public String getServiceInterface();
public DependencyType getType();
public String getVersion();
public enum DependencyType {
REQUIRED, // Deployment fails if not available
OPTIONAL // Deployment succeeds even if absent
}
}public class DependencyResolver {
public void validateDependencies(String applicationId, List<ApplicationDependency> dependencies);
public List<String> computeStartupOrder(String applicationId);
public boolean hasCircularDependency(String applicationId);
}public interface HealthCheck {
boolean isHealthy();
String getHealthMessage();
}