Apache Tuscany is an open-source Java implementation of the Service Component Architecture (SCA) specification that assembles applications from loosely coupled service components connected by wires, supporting multiple binding types including HTTP, JMS, WebService, and RMI for cross-component and cross-node communication. When a Tuscany composite fails to deploy after a component class change, the domain runtime silently marks affected wires as broken — upstream components continue running while all downstream service calls throw ServiceRuntimeException without logging the missing component; when a Tuscany JMS binding loses its broker connection during a rolling infrastructure restart, service calls that route through that binding queue indefinitely instead of failing fast, saturating the calling component's thread pool until it becomes unresponsive; when an SCA property injection fails because a required <property> value is missing from the composite XML, the affected component initializes with null property values that produce NullPointerException mid-request — all while the node health check endpoint reports the runtime as running. These are silent routing and correctness failures that external monitoring catches before service degradation cascades across composite boundaries.
Vigilmon gives you external visibility into Tuscany SOA application health through HTTP probe monitoring and heartbeat monitors. This tutorial covers both.
Why Tuscany Applications Need External Monitoring
Tuscany's failure modes span the composite deployment, binding execution, and domain coordination layers:
- Composite deployment failure: when a
.compositedescriptor references a component implementation class that fails to load, Tuscany's domain silently skips the affected composite — wires that reference services from the failed composite produceServiceUnavailableExceptionat call time, not at deployment time - Binding connection loss: Tuscany's JMS, WebService, and RMI bindings maintain persistent connections; when those connections drop during infrastructure maintenance, bound services become unreachable but Tuscany does not automatically reconnect — subsequent calls fail until a manual redeploy or runtime restart
- SCA wire propagation failure: a reference wire that crosses node boundaries in a distributed Tuscany domain relies on the domain registry to resolve the remote service endpoint; when the registry becomes inconsistent after a node restart, some cross-node wires resolve correctly while others throw
ServiceNotFoundExceptionnon-deterministically - Property injection NPE cascade: an SCA
<property>defined in the composite XML but absent from the deployment configuration causes the receiving component to initialize with a null field — since property injection failure is not a fatal deployment error in Tuscany, the component starts and fails only when the null property is accessed - Contribution classpath conflict: multiple SCA contributions deployed to the same Tuscany runtime share a classloading hierarchy; a jar version conflict between contributions causes
ClassCastExceptionwhen objects are passed across contribution boundaries through wires - Conversation lifecycle leak: Tuscany's conversational bindings maintain conversation state between service calls; a conversation that is not properly ended (via
@EndsConversation) retains server-side state indefinitely, eventually exhausting the conversation store
External monitoring with Vigilmon adds:
- Proactive alerting when Tuscany composite services stop responding successfully
- Composite health visibility through a dedicated domain status endpoint
- Heartbeat monitoring so you know when scheduled integration jobs or domain maintenance tasks stop completing
- Multi-region probe consensus that separates transient binding reconnection delays from genuine composite deployment failures
Step 1: Build a Tuscany Health Endpoint
Tuscany does not ship a built-in health endpoint. Add a composite service component that checks domain and binding status.
Tuscany SCA Health Service Component
First, define the health service interface:
// health/HealthService.java
package com.yourorg.health;
import org.oasisopen.sca.annotation.Remotable;
@Remotable
public interface HealthService {
HealthStatus check();
}
// health/HealthStatus.java
package com.yourorg.health;
public class HealthStatus {
public String status;
public String detail;
public int compositeCount;
public HealthStatus(String status, String detail, int compositeCount) {
this.status = status;
this.detail = detail;
this.compositeCount = compositeCount;
}
}
Implement the health service:
// health/HealthServiceImpl.java
package com.yourorg.health;
import org.apache.tuscany.sca.Node;
import org.apache.tuscany.sca.runtime.TuscanyRuntime;
import org.oasisopen.sca.annotation.Reference;
import org.oasisopen.sca.annotation.Service;
import java.util.Collection;
@Service(HealthService.class)
public class HealthServiceImpl implements HealthService {
// Injected by SCA runtime if a domain reference is available
// In minimal setups, use static accessor instead
private TuscanyRuntime runtime;
@Override
public HealthStatus check() {
try {
// Verify the Tuscany node is running and composites are deployed
if (runtime == null) {
// Fallback: try to create a minimal node to verify runtime availability
Node node = TuscanyRuntime.newInstance().createNode();
node.stop();
return new HealthStatus("ok", "tuscany runtime available", 0);
}
return new HealthStatus("ok", "tuscany runtime operational", 1);
} catch (Exception e) {
return new HealthStatus("down", e.getMessage(), 0);
}
}
}
Expose the health service via an HTTP binding in the composite descriptor:
<!-- health.composite -->
<?xml version="1.0" encoding="UTF-8"?>
<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912"
targetNamespace="http://yourorg.com/health"
name="health">
<component name="HealthServiceComponent">
<implementation.java class="com.yourorg.health.HealthServiceImpl"/>
<service name="HealthService">
<binding.http uri="/health/tuscany"/>
</service>
</component>
</composite>
Spring Boot Wrapper for Tuscany Runtime
// health/TuscanyHealthIndicator.java
package com.yourorg.health;
import org.apache.tuscany.sca.Node;
import org.apache.tuscany.sca.runtime.TuscanyRuntime;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class TuscanyHealthIndicator implements HealthIndicator {
private final Node tuscanyNode;
public TuscanyHealthIndicator(Node tuscanyNode) {
this.tuscanyNode = tuscanyNode;
}
@Override
public Health health() {
try {
if (tuscanyNode == null) {
return Health.down()
.withDetail("tuscany", "node not initialized")
.build();
}
// Probe: invoke a known composite service endpoint
HealthService healthService = tuscanyNode.getService(
HealthService.class, "HealthServiceComponent/HealthService"
);
HealthStatus status = healthService.check();
if (!"ok".equals(status.status)) {
return Health.down()
.withDetail("tuscany", status.detail)
.build();
}
return Health.up()
.withDetail("tuscany", status.detail)
.withDetail("composites", status.compositeCount)
.build();
} catch (Exception e) {
return Health.down(e)
.withDetail("tuscany", "composite health check failed")
.build();
}
}
}
Expose the Actuator health endpoint in application.properties:
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=health,info
The /actuator/health response will include the Tuscany component:
{
"status": "UP",
"components": {
"tuscany": {
"status": "UP",
"details": {
"tuscany": "tuscany runtime operational",
"composites": 4
}
}
}
}
Standalone Servlet Health Endpoint
// servlet/TuscanyHealthServlet.java
package com.yourorg.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
@WebServlet("/health/tuscany")
public class TuscanyHealthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
Map<String, Object> result = new LinkedHashMap<>();
resp.setContentType("application/json");
try {
// Check if the Tuscany servlet context attribute is present
Object runtime = getServletContext().getAttribute("tuscanyRuntime");
if (runtime == null) {
result.put("status", "down");
result.put("detail", "Tuscany runtime not found in servlet context");
resp.setStatus(503);
} else {
result.put("status", "ok");
result.put("detail", "Tuscany runtime available");
resp.setStatus(200);
}
} catch (Exception e) {
result.put("status", "down");
result.put("error", e.getMessage());
resp.setStatus(503);
}
resp.getWriter().write(toJson(result));
}
private String toJson(Map<String, Object> map) {
StringBuilder sb = new StringBuilder("{");
map.forEach((k, v) -> {
if (sb.length() > 1) sb.append(",");
sb.append("\"").append(k).append("\":\"").append(v).append("\"");
});
sb.append("}");
return sb.toString();
}
}
Node.js Sidecar
// health/tuscany.js
const express = require('express');
const app = express();
const TUSCANY_APP_BASE = process.env.TUSCANY_APP_BASE || 'http://localhost:8080';
app.get('/health/tuscany-proxy', async (req, res) => {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
const response = await fetch(`${TUSCANY_APP_BASE}/health/tuscany`, {
signal: controller.signal,
});
clearTimeout(timeout);
const body = await response.json();
const ok = response.ok && body?.status === 'ok';
return res.status(ok ? 200 : 503).json({ status: ok ? 'ok' : 'degraded', upstream: body });
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3023);
Python Sidecar
# health_tuscany.py
import os
import urllib.request
import json
from flask import Flask, jsonify
app = Flask(__name__)
TUSCANY_APP_BASE = os.environ.get('TUSCANY_APP_BASE', 'http://localhost:8080')
@app.route('/health/tuscany-proxy')
def health():
try:
url = f'{TUSCANY_APP_BASE}/health/tuscany'
with urllib.request.urlopen(url, timeout=4) as resp:
body = json.loads(resp.read())
ok = body.get('status') == 'ok'
return jsonify({'status': 'ok' if ok else 'degraded', 'upstream': body}), 200 if ok else 503
except Exception as e:
return jsonify({'status': 'down', 'error': str(e)}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3023)
Step 2: Configure Vigilmon HTTP Monitor for Tuscany
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Tuscany health endpoint:
https://app.your-org.example.com/health/tuscany - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
3000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for a composite service endpoint to confirm end-to-end SCA wire routing:
- URL:
https://app.your-org.example.com/api/composite/ping(a lightweight composite service call that crosses at least one SCA wire) - Expected:
200 - Interval: 2 minutes
- Alert channel: PagerDuty P1 — failure here means SCA wires are broken or binding connections have been dropped
For applications with multiple Tuscany nodes in a distributed domain, add one monitor per node health endpoint:
| Endpoint | What it catches |
|---|---|
| /health/tuscany | Tuscany runtime availability, composite deployment |
| /api/composite/ping | End-to-end SCA wire routing and binding execution |
| /actuator/health (Spring) | Spring wrapper health including Tuscany node status |
Vigilmon's multi-region probing filters single-region network blips from genuine binding connection failures.
Step 3: Heartbeat Monitoring for Integration Jobs and Domain Maintenance
HTTP health checks verify the Tuscany composite services are reachable but not the health of background jobs that handle integration workflows or domain maintenance. Tuscany SOA applications often run:
- Message replay jobs: scheduled jobs that replay failed JMS messages from the dead letter queue back through the affected Tuscany service binding
- Domain registry reconciliation jobs: periodic jobs that verify the distributed domain registry is consistent across all Tuscany nodes and repair inconsistent wire resolutions
- Contribution redeployment jobs: automated jobs that redeploy updated SCA contributions during maintenance windows
Vigilmon heartbeat monitors detect when these jobs stop executing.
Set Up Heartbeat Monitors
For message replay job liveness:
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
tuscany-message-replay - Set the expected interval: 30 minutes
- Set the grace period: 60 minutes
- Save — copy the heartbeat URL
For domain registry reconciliation job liveness:
- Create another Heartbeat monitor named
tuscany-domain-reconcile - Set the expected interval: 1 hour
- Set the grace period: 90 minutes
- Save — copy the heartbeat URL
Wire Heartbeats Into Scheduled Jobs
// scheduler/MessageReplayJob.java
package com.yourorg.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.*;
@Service
public class MessageReplayJob {
private final JmsDeadLetterService deadLetterService;
private final HttpClient http = HttpClient.newHttpClient();
private final String heartbeatUrl = System.getenv("VIGILMON_MESSAGE_REPLAY_HEARTBEAT_URL");
public MessageReplayJob(JmsDeadLetterService deadLetterService) {
this.deadLetterService = deadLetterService;
}
@Scheduled(fixedDelay = 1800000) // every 30 minutes
public void replayFailedMessages() {
try {
int replayed = deadLetterService.replayDeadLetterMessages();
// Log replayed count; alert on persistent failures
pingHeartbeat();
} catch (Exception e) {
// log — do NOT ping heartbeat on failure
throw new RuntimeException("Message replay job failed", e);
}
}
private void pingHeartbeat() {
if (heartbeatUrl == null || heartbeatUrl.isBlank()) return;
try {
http.sendAsync(
HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
For Quartz-based domain reconciliation jobs:
// quartz/DomainReconcileJob.java
package com.yourorg.quartz;
import org.apache.tuscany.sca.Node;
import org.quartz.*;
import java.net.URI;
import java.net.http.*;
public class DomainReconcileJob implements Job {
private final String heartbeatUrl = System.getenv("VIGILMON_DOMAIN_RECONCILE_HEARTBEAT_URL");
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
Node node = (Node) context.getMergedJobDataMap().get("tuscanyNode");
// Run domain registry reconciliation
reconcileDomainRegistry(node);
pingHeartbeat();
} catch (Exception e) {
throw new JobExecutionException("Domain reconciliation failed", e);
}
}
private void reconcileDomainRegistry(Node node) {
// Verify cross-node wire resolutions are consistent
}
private void pingHeartbeat() {
if (heartbeatUrl == null) return;
try {
HttpClient.newHttpClient().sendAsync(
HttpRequest.newBuilder(URI.create(heartbeatUrl)).GET().build(),
HttpResponse.BodyHandlers.discarding()
);
} catch (Exception ignored) {}
}
}
For shell-based domain maintenance scripts:
#!/bin/bash
# tuscany-domain-reconcile.sh
set -e
java -jar /opt/app/tuscany-admin.jar --reconcile-domain --node-urls="$TUSCANY_NODE_URLS"
if [ -n "$VIGILMON_DOMAIN_RECONCILE_HEARTBEAT_URL" ]; then
curl -s "$VIGILMON_DOMAIN_RECONCILE_HEARTBEAT_URL" > /dev/null 2>&1 || true
fi
Step 4: Alert Routing for Tuscany Failures
Tuscany failures range from hard composite deployment failures that silently break specific service routes to subtle binding reconnection issues that cause JMS-backed services to become unreachable until a manual restart. Route alerts by impact:
| Monitor | Alert Channel | Priority |
|---|---|---|
| HTTP: /health/tuscany | Slack + PagerDuty | P1 |
| HTTP: composite service endpoint | Slack + PagerDuty | P1 |
| HTTP: actuator health (Spring) | Slack + PagerDuty | P1 |
| Heartbeat: message replay job | Slack | P2 |
| Heartbeat: domain reconciliation job | Slack + email | P2 |
| Heartbeat: contribution redeploy job | Slack + email | P2 |
Set response time thresholds as early warning signals:
- Alert at
1000msfor the Tuscany health endpoint (slow responses indicate domain registry pressure or binding reconnection overhead) - Alert at
2000msfor composite service endpoints (slow responses indicate wire traversal latency or binding pool saturation) - Alert at
60 minutesgap in message replay heartbeat (dead letter queue is growing without replay) - Alert at
90 minutesgap in domain reconciliation heartbeat during the expected hourly reconciliation window
For production Tuscany deployments backing enterprise SOA integrations, set up a status page in Vigilmon showing composite service availability — this gives operations teams a single pane of glass to correlate Tuscany binding alerts with upstream JMS broker or WebService endpoint incidents before users report integration failures.
Summary
Tuscany failures typically present as silent routing breaks — a composite deployment failure causes specific SCA wire traversals to fail while the node itself remains running, or a binding connection drop causes JMS-backed service calls to queue indefinitely. External monitoring catches these earlier than your users do:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /health/tuscany | Tuscany runtime, composite deployment, node status |
| HTTP monitor on composite endpoint | End-to-end SCA wire routing and binding execution |
| HTTP monitor on actuator health | Spring wrapper health including Tuscany node |
| Heartbeat monitor (message replay) | Dead letter message replay job liveness |
| Heartbeat monitor (domain reconcile) | Domain registry reconciliation job liveness |
Get started free at vigilmon.online — your first Tuscany SOA monitor is running in under two minutes.