tutorial

How to Monitor Apache Tuscany SOA Health with Vigilmon

Apache Tuscany composite deployment failures, binding resolution errors, and SCA wire propagation breakdowns are invisible until your service-oriented architecture stops routing requests across component boundaries. Learn how to monitor Tuscany SOA application availability, composite service health, and scheduled integration job liveness with Vigilmon HTTP probes and heartbeat monitors.

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 .composite descriptor 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 produce ServiceUnavailableException at 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 ServiceNotFoundException non-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 ClassCastException when 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

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS
  3. Set the URL to your Tuscany health endpoint: https://app.your-org.example.com/health/tuscany
  4. Set the check interval to 1 minute
  5. Under Expected response, configure:
    • Status code: 200
    • Response body contains: "status":"ok"
    • Response time threshold: 3000ms
  6. Under Alert channels, assign your Slack or PagerDuty channel
  7. 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:

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat
  2. Set the name: tuscany-message-replay
  3. Set the expected interval: 30 minutes
  4. Set the grace period: 60 minutes
  5. Save — copy the heartbeat URL

For domain registry reconciliation job liveness:

  1. Create another Heartbeat monitor named tuscany-domain-reconcile
  2. Set the expected interval: 1 hour
  3. Set the grace period: 90 minutes
  4. 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 1000ms for the Tuscany health endpoint (slow responses indicate domain registry pressure or binding reconnection overhead)
  • Alert at 2000ms for composite service endpoints (slow responses indicate wire traversal latency or binding pool saturation)
  • Alert at 60 minutes gap in message replay heartbeat (dead letter queue is growing without replay)
  • Alert at 90 minutes gap 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.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →