CDAP Java Client library providing programmatic APIs for interacting with the CDAP platform
—
The ServiceClient provides comprehensive user service interactions including endpoint discovery, availability checking, routing configuration, and direct service method calls. Services are HTTP-based programs that expose RESTful endpoints.
public class ServiceClient {
// Constructors
public ServiceClient(ClientConfig config);
public ServiceClient(ClientConfig config, RESTClient restClient);
// Service information methods
public ServiceSpecification get(ProgramId service);
public List<ServiceHttpEndpoint> getEndpoints(ServiceId service);
public void checkAvailability(ServiceId service);
public URL getVersionedServiceURL(ServiceId service);
public URL getServiceURL(ServiceId service);
// Route configuration methods
public Map<String, Integer> getRouteConfig(ServiceId serviceId);
public void storeRouteConfig(ServiceId serviceId, Map<String, Integer> routeConfig);
public void deleteRouteConfig(ServiceId serviceId);
// Service call methods
public HttpResponse callServiceMethod(ServiceId serviceId, String methodPath);
}public class ServiceSpecification {
public String getName();
public String getClassName();
public String getDescription();
public Map<String, HttpServiceHandlerSpecification> getHandlers();
public ResourceSpecification getResources();
}
public class ServiceHttpEndpoint {
public String getMethod();
public String getPath();
}
public class ServiceId {
public static ServiceId of(ApplicationId application, String service);
public ApplicationId getApplication();
public String getService();
}
public class HttpResponse {
public int getResponseCode();
public Map<String, List<String>> getHeaders();
public String getResponseMessage();
public byte[] getResponseBody();
public String getResponseBodyAsString();
}// Get service specification
ApplicationId appId = ApplicationId.of(namespace, "web-analytics", "1.0.0");
ProgramId serviceProgram = ProgramId.of(appId, ProgramType.SERVICE, "analytics-service");
ServiceSpecification spec = serviceClient.get(serviceProgram);
System.out.println("Service: " + spec.getName());
System.out.println("Class: " + spec.getClassName());
System.out.println("Description: " + spec.getDescription());
// Get handlers information
Map<String, HttpServiceHandlerSpecification> handlers = spec.getHandlers();
for (Map.Entry<String, HttpServiceHandlerSpecification> entry : handlers.entrySet()) {
System.out.println("Handler: " + entry.getKey());
HttpServiceHandlerSpecification handler = entry.getValue();
System.out.println(" Class: " + handler.getClassName());
System.out.println(" Endpoints: " + handler.getEndpoints().size());
}
// Get resource requirements
ResourceSpecification resources = spec.getResources();
System.out.println("Memory: " + resources.getMemoryMB() + " MB");
System.out.println("VCores: " + resources.getVirtualCores());// Get all endpoints for a service
ServiceId serviceId = ServiceId.of(appId, "analytics-service");
List<ServiceHttpEndpoint> endpoints = serviceClient.getEndpoints(serviceId);
System.out.println("Service endpoints:");
for (ServiceHttpEndpoint endpoint : endpoints) {
System.out.println(" " + endpoint.getMethod() + " " + endpoint.getPath());
}
// Common endpoint patterns
for (ServiceHttpEndpoint endpoint : endpoints) {
String method = endpoint.getMethod();
String path = endpoint.getPath();
if ("GET".equals(method) && path.contains("/status")) {
System.out.println("Health check endpoint: " + method + " " + path);
} else if ("POST".equals(method)) {
System.out.println("Data submission endpoint: " + method + " " + path);
} else if ("GET".equals(method) && path.contains("/metrics")) {
System.out.println("Metrics endpoint: " + method + " " + path);
}
}// Check if service is available
try {
serviceClient.checkAvailability(serviceId);
System.out.println("Service is available: " + serviceId.getService());
} catch (ServiceUnavailableException e) {
System.err.println("Service unavailable: " + serviceId.getService());
} catch (ServiceNotFoundException e) {
System.err.println("Service not found: " + serviceId.getService());
}
// Wait for service availability
public void waitForServiceAvailability(ServiceId serviceId, int maxAttempts, long delayMs) {
for (int i = 0; i < maxAttempts; i++) {
try {
serviceClient.checkAvailability(serviceId);
System.out.println("Service is now available: " + serviceId.getService());
return;
} catch (ServiceUnavailableException e) {
if (i == maxAttempts - 1) {
throw new RuntimeException("Service did not become available after " + maxAttempts + " attempts");
}
System.out.println("Service not available, attempt " + (i + 1) + "/" + maxAttempts);
try {
Thread.sleep(delayMs);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for service", ie);
}
}
}
}// Get service URLs
URL versionedUrl = serviceClient.getVersionedServiceURL(serviceId);
URL unversionedUrl = serviceClient.getServiceURL(serviceId);
System.out.println("Versioned URL: " + versionedUrl);
System.out.println("Unversioned URL: " + unversionedUrl);
// Build endpoint URLs
String baseUrl = unversionedUrl.toString();
for (ServiceHttpEndpoint endpoint : endpoints) {
String endpointUrl = baseUrl + endpoint.getPath();
System.out.println(endpoint.getMethod() + " -> " + endpointUrl);
}// Build URLs for different environments
public class ServiceURLBuilder {
private final ServiceClient serviceClient;
public ServiceURLBuilder(ServiceClient serviceClient) {
this.serviceClient = serviceClient;
}
public String buildEndpointURL(ServiceId serviceId, String path, boolean versioned) {
try {
URL baseUrl = versioned ?
serviceClient.getVersionedServiceURL(serviceId) :
serviceClient.getServiceURL(serviceId);
return baseUrl.toString() + (path.startsWith("/") ? path : "/" + path);
} catch (Exception e) {
throw new RuntimeException("Error building service URL", e);
}
}
public String buildParameterizedURL(ServiceId serviceId, String pathTemplate, Object... params) {
String path = String.format(pathTemplate, params);
return buildEndpointURL(serviceId, path, false);
}
}
// Usage
ServiceURLBuilder urlBuilder = new ServiceURLBuilder(serviceClient);
String userUrl = urlBuilder.buildParameterizedURL(serviceId, "/users/%s", "user123");
String metricsUrl = urlBuilder.buildEndpointURL(serviceId, "/metrics", false);// Get current route configuration
Map<String, Integer> currentRoutes = serviceClient.getRouteConfig(serviceId);
System.out.println("Current routes: " + currentRoutes);
// Configure traffic routing between service versions
Map<String, Integer> routeConfig = Map.of(
"v1.0.0", 80, // 80% traffic to v1.0.0
"v1.1.0", 20 // 20% traffic to v1.1.0 (canary deployment)
);
serviceClient.storeRouteConfig(serviceId, routeConfig);
System.out.println("Updated route configuration for gradual rollout");
// Blue-green deployment routing
Map<String, Integer> blueGreenRoutes = Map.of(
"blue", 0, // Old version (no traffic)
"green", 100 // New version (all traffic)
);
serviceClient.storeRouteConfig(serviceId, blueGreenRoutes);// Gradual traffic migration
public void performGradualMigration(ServiceId serviceId, String oldVersion, String newVersion) {
int[] trafficSplits = {90, 80, 60, 40, 20, 0}; // Gradual reduction of old version traffic
for (int oldTraffic : trafficSplits) {
int newTraffic = 100 - oldTraffic;
Map<String, Integer> routes = Map.of(
oldVersion, oldTraffic,
newVersion, newTraffic
);
try {
serviceClient.storeRouteConfig(serviceId, routes);
System.out.println("Route config: " + oldVersion + "=" + oldTraffic + "%, " +
newVersion + "=" + newTraffic + "%");
// Wait and monitor before next step
Thread.sleep(300000); // 5 minutes
// Check service health before continuing
serviceClient.checkAvailability(serviceId);
} catch (Exception e) {
System.err.println("Error during migration step: " + e.getMessage());
// Rollback to previous configuration
Map<String, Integer> rollbackRoutes = Map.of(oldVersion, 100);
serviceClient.storeRouteConfig(serviceId, rollbackRoutes);
throw new RuntimeException("Migration failed, rolled back", e);
}
}
System.out.println("Migration completed successfully");
}
// Remove route configuration (default routing)
serviceClient.deleteRouteConfig(serviceId);
System.out.println("Route configuration removed, using default routing");// Make direct calls to service methods
try {
// GET request
HttpResponse response = serviceClient.callServiceMethod(serviceId, "/status");
System.out.println("Status response: " + response.getResponseCode());
System.out.println("Body: " + response.getResponseBodyAsString());
// Check response headers
Map<String, List<String>> headers = response.getHeaders();
if (headers.containsKey("Content-Type")) {
System.out.println("Content-Type: " + headers.get("Content-Type").get(0));
}
} catch (ServiceUnavailableException e) {
System.err.println("Service call failed - service unavailable");
} catch (IOException e) {
System.err.println("Network error during service call: " + e.getMessage());
}// Service interaction with custom HTTP client
public class ServiceInteractor {
private final ServiceClient serviceClient;
private final ServiceId serviceId;
public ServiceInteractor(ServiceClient serviceClient, ServiceId serviceId) {
this.serviceClient = serviceClient;
this.serviceId = serviceId;
}
public String getServiceStatus() {
try {
HttpResponse response = serviceClient.callServiceMethod(serviceId, "/status");
if (response.getResponseCode() == 200) {
return response.getResponseBodyAsString();
} else {
throw new RuntimeException("Service status check failed: " + response.getResponseCode());
}
} catch (Exception e) {
throw new RuntimeException("Error checking service status", e);
}
}
public Map<String, Object> getServiceMetrics() {
try {
HttpResponse response = serviceClient.callServiceMethod(serviceId, "/metrics");
if (response.getResponseCode() == 200) {
String jsonResponse = response.getResponseBodyAsString();
// Parse JSON response (using your preferred JSON library)
return parseJsonResponse(jsonResponse);
} else {
throw new RuntimeException("Metrics retrieval failed: " + response.getResponseCode());
}
} catch (Exception e) {
throw new RuntimeException("Error retrieving service metrics", e);
}
}
public boolean isHealthy() {
try {
serviceClient.checkAvailability(serviceId);
HttpResponse healthResponse = serviceClient.callServiceMethod(serviceId, "/health");
return healthResponse.getResponseCode() == 200;
} catch (Exception e) {
return false;
}
}
private Map<String, Object> parseJsonResponse(String json) {
// Implement JSON parsing using your preferred library (Jackson, Gson, etc.)
// This is a placeholder implementation
return Map.of("status", "parsed");
}
}// Comprehensive service health monitoring
public class ServiceHealthMonitor {
private final ServiceClient serviceClient;
private final ServiceId serviceId;
public ServiceHealthMonitor(ServiceClient serviceClient, ServiceId serviceId) {
this.serviceClient = serviceClient;
this.serviceId = serviceId;
}
public ServiceHealthStatus checkHealth() {
ServiceHealthStatus.Builder statusBuilder = ServiceHealthStatus.builder()
.serviceId(serviceId)
.timestamp(System.currentTimeMillis());
try {
// Check basic availability
serviceClient.checkAvailability(serviceId);
statusBuilder.available(true);
// Check endpoints
List<ServiceHttpEndpoint> endpoints = serviceClient.getEndpoints(serviceId);
statusBuilder.endpointCount(endpoints.size());
// Test health endpoint if available
for (ServiceHttpEndpoint endpoint : endpoints) {
if ("GET".equals(endpoint.getMethod()) &&
(endpoint.getPath().contains("/health") || endpoint.getPath().contains("/status"))) {
HttpResponse response = serviceClient.callServiceMethod(serviceId, endpoint.getPath());
statusBuilder.healthEndpointStatus(response.getResponseCode());
statusBuilder.healthEndpointResponse(response.getResponseBodyAsString());
break;
}
}
// Check route configuration
Map<String, Integer> routes = serviceClient.getRouteConfig(serviceId);
statusBuilder.routeConfiguration(routes);
statusBuilder.healthy(true);
} catch (ServiceNotFoundException e) {
statusBuilder.available(false).healthy(false).error("Service not found");
} catch (ServiceUnavailableException e) {
statusBuilder.available(false).healthy(false).error("Service unavailable");
} catch (Exception e) {
statusBuilder.available(false).healthy(false).error("Error: " + e.getMessage());
}
return statusBuilder.build();
}
public void monitorContinuously(long intervalMs, HealthStatusCallback callback) {
Thread monitorThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
ServiceHealthStatus status = checkHealth();
callback.onHealthStatus(status);
Thread.sleep(intervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
callback.onError(e);
}
}
});
monitorThread.setDaemon(true);
monitorThread.start();
}
@FunctionalInterface
public interface HealthStatusCallback {
void onHealthStatus(ServiceHealthStatus status);
default void onError(Exception e) {
System.err.println("Health monitoring error: " + e.getMessage());
}
}
}
// Health status data class
public class ServiceHealthStatus {
private final ServiceId serviceId;
private final long timestamp;
private final boolean available;
private final boolean healthy;
private final int endpointCount;
private final Integer healthEndpointStatus;
private final String healthEndpointResponse;
private final Map<String, Integer> routeConfiguration;
private final String error;
// Constructor, getters, and builder implementation
public static Builder builder() {
return new Builder();
}
public static class Builder {
// Builder implementation
}
}Service management operations may throw these exceptions:
try {
ServiceSpecification spec = serviceClient.get(serviceProgram);
System.out.println("Service specification retrieved");
} catch (ServiceNotFoundException e) {
System.err.println("Service not found: " + serviceId.getService());
} catch (UnauthorizedException e) {
System.err.println("No permission to access service: " + e.getMessage());
} catch (IOException e) {
System.err.println("Network error: " + e.getMessage());
}// Good: Comprehensive service management with error handling and monitoring
public class ServiceManager {
private final ServiceClient serviceClient;
private final ServiceId serviceId;
private final ServiceHealthMonitor healthMonitor;
public ServiceManager(ServiceClient serviceClient, ServiceId serviceId) {
this.serviceClient = serviceClient;
this.serviceId = serviceId;
this.healthMonitor = new ServiceHealthMonitor(serviceClient, serviceId);
}
public void deployWithHealthCheck(Map<String, Integer> routeConfig) {
try {
// Store new route configuration
serviceClient.storeRouteConfig(serviceId, routeConfig);
// Wait for deployment to stabilize
Thread.sleep(30000); // 30 seconds
// Verify service health
ServiceHealthStatus status = healthMonitor.checkHealth();
if (!status.isHealthy()) {
// Rollback on failure
System.err.println("Service unhealthy after deployment, rolling back");
serviceClient.deleteRouteConfig(serviceId); // Use default routing
throw new RuntimeException("Deployment failed health check");
}
System.out.println("Deployment successful and service is healthy");
} catch (Exception e) {
System.err.println("Deployment failed: " + e.getMessage());
throw new RuntimeException("Service deployment failed", e);
}
}
public String callServiceSafely(String methodPath, int maxRetries) {
Exception lastException = null;
for (int i = 0; i < maxRetries; i++) {
try {
serviceClient.checkAvailability(serviceId);
HttpResponse response = serviceClient.callServiceMethod(serviceId, methodPath);
if (response.getResponseCode() == 200) {
return response.getResponseBodyAsString();
} else if (response.getResponseCode() >= 500) {
// Server error - retry
Thread.sleep(1000 * (i + 1)); // Exponential backoff
continue;
} else {
// Client error - don't retry
throw new RuntimeException("Service call failed: " + response.getResponseCode());
}
} catch (Exception e) {
lastException = e;
if (i < maxRetries - 1) {
System.out.println("Service call failed, retrying... (" + (i + 1) + "/" + maxRetries + ")");
try {
Thread.sleep(1000 * (i + 1));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry", ie);
}
}
}
}
throw new RuntimeException("Service call failed after " + maxRetries + " attempts", lastException);
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-cdap-cdap--cdap-client