4.3 SOAP-protocol

Kent het SOAP-protocol voor webservices (structuur, WSDL, security, foutafhandeling).

Wat is SOAP?

SOAP (Simple Object Access Protocol, later hernoemd naar Service Oriented Architecture Protocol) is een messaging-protocol voor het uitwisselen van gestructureerde informatie in web services. SOAP is een W3C-standaard die zwaar wordt gebruikt in enterprise- en overheidsomgevingen voor betrouwbare, veilige communicatie tussen systemen.

Kerneigenschappen:

  • Protocol-agnostic: Werkt over HTTP, HTTPS, SMTP, TCP
  • Platform-independent: Kan tussen verschillende systemen communiceren
  • Extensible: Uitbreidbaar met headers en security-features
  • Standardized: Strikte W3C-specificatie met tooling-support

SOAP-berichtstructuur

Basis SOAP-envelope

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:tns="http://example.org/persoon-service">
    
    <!-- Optionele headers -->
    <soap:Header>
        <tns:Authentication>
            <tns:Username>gemeente_amsterdam</tns:Username>
            <tns:Token>abc123-xyz789</tns:Token>
        </tns:Authentication>
    </soap:Header>
    
    <!-- Verplichte body -->
    <soap:Body>
        <tns:GetPersoonRequest>
            <tns:BSN>123456789</tns:BSN>
        </tns:GetPersoonRequest>
    </soap:Body>
</soap:Envelope>

SOAP-response

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:tns="http://example.org/persoon-service">
    
    <soap:Body>
        <tns:GetPersoonResponse>
            <tns:Persoon>
                <tns:BSN>123456789</tns:BSN>
                <tns:Voornaam>Jan</tns:Voornaam>
                <tns:Achternaam>Berg</tns:Achternaam>
                <tns:Geboortedatum>1985-03-15</tns:Geboortedatum>
                <tns:Adres>
                    <tns:Straat>Hoofdstraat 42</tns:Straat>
                    <tns:Postcode>1234AB</tns:Postcode>
                    <tns:Plaats>Amsterdam</tns:Plaats>
                </tns:Adres>
            </tns:Persoon>
        </tns:GetPersoonResponse>
    </soap:Body>
</soap:Envelope>

SOAP Fault (foutafhandeling)

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <soap:Fault>
            <soap:Code>
                <soap:Value>soap:Client</soap:Value>
                <soap:Subcode>
                    <soap:Value>tns:InvalidBSN</soap:Value>
                </soap:Subcode>
            </soap:Code>
            <soap:Reason>
                <soap:Text xml:lang="nl">
                    BSN '12345678X' is geen geldig Burgerservicenummer
                </soap:Text>
            </soap:Reason>
            <soap:Detail>
                <tns:ValidationError>
                    <tns:Field>BSN</tns:Field>
                    <tns:Message>BSN moet uit precies 9 cijfers bestaan</tns:Message>
                    <tns:Code>BSN_INVALID_FORMAT</tns:Code>
                </tns:ValidationError>
            </soap:Detail>
        </soap:Fault>
    </soap:Body>
</soap:Envelope>

WSDL (Web Service Description Language)

WSDL beschrijft de interface van een SOAP web service - welke operaties beschikbaar zijn, hoe berichten gestructureerd zijn, en hoe de service te bereiken is.

WSDL-structuur

<?xml version="1.0" encoding="UTF-8"?>
<definitions 
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://example.org/persoon-service"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://example.org/persoon-service">

    <!-- Data types (XSD schema) -->
    <types>
        <xsd:schema targetNamespace="http://example.org/persoon-service">
            <xsd:complexType name="Persoon">
                <xsd:sequence>
                    <xsd:element name="BSN" type="xsd:string"/>
                    <xsd:element name="Voornaam" type="xsd:string"/>
                    <xsd:element name="Achternaam" type="xsd:string"/>
                    <xsd:element name="Geboortedatum" type="xsd:date"/>
                </xsd:sequence>
            </xsd:complexType>
            
            <xsd:element name="GetPersoonRequest">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="BSN" type="xsd:string"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            
            <xsd:element name="GetPersoonResponse">
                <xsd:complexType>
                    <xsd:sequence>
                        <xsd:element name="Persoon" type="tns:Persoon"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
        </xsd:schema>
    </types>

    <!-- Abstract message definitions -->
    <message name="GetPersoonRequestMessage">
        <part name="parameters" element="tns:GetPersoonRequest"/>
    </message>
    <message name="GetPersoonResponseMessage">
        <part name="parameters" element="tns:GetPersoonResponse"/>
    </message>

    <!-- Abstract interface (operations) -->
    <portType name="PersoonServiceInterface">
        <operation name="GetPersoon">
            <input message="tns:GetPersoonRequestMessage"/>
            <output message="tns:GetPersoonResponseMessage"/>
        </operation>
    </portType>

    <!-- Concrete protocol binding -->
    <binding name="PersoonServiceBinding" type="tns:PersoonServiceInterface">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="GetPersoon">
            <soap:operation soapAction="http://example.org/persoon-service/GetPersoon"/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>

    <!-- Concrete endpoint -->
    <service name="PersoonService">
        <port name="PersoonServicePort" binding="tns:PersoonServiceBinding">
            <soap:address location="https://api.gemeente.nl/soap/persoon-service"/>
        </port>
    </service>
</definitions>

WSDL-gebruik voor code-generation

Java (met wsimport):

# Generate Java classes van WSDL
wsimport -keep -s src/main/java https://api.gemeente.nl/soap/persoon-service?wsdl

# Usage in code
PersoonService service = new PersoonService();
PersoonServiceInterface port = service.getPersoonServicePort();
GetPersoonResponse response = port.getPersoon(request);

C# (met svcutil of Visual Studio):

# Generate C# proxy classes
svcutil https://api.gemeente.nl/soap/persoon-service?wsdl /out:PersoonServiceProxy.cs

# Usage in code
var client = new PersoonServiceClient();
var response = await client.GetPersoonAsync(new GetPersoonRequest 
{ 
    BSN = "123456789" 
});

SOAP Security (WS-Security)

Username Token Profile

<soap:Header>
    <wsse:Security 
        xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
        xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        
        <wsse:UsernameToken wsu:Id="UsernameToken-1">
            <wsse:Username>gemeente_amsterdam</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">
                R1VrV0lPMCsvOUI3Ylc1M2V1VE9vUT09
            </wsse:Password>
            <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">
                MTIzNDU2Nzg5MA==
            </wsse:Nonce>
            <wsu:Created>2024-03-05T14:30:00Z</wsu:Created>
        </wsse:UsernameToken>
    </wsse:Security>
</soap:Header>

X.509 Certificate Security

<soap:Header>
    <wsse:Security>
        <wsse:BinarySecurityToken
            ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
            EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
            wsu:Id="X509Token">
            MIICdTCCAd4CAQAwDQ...
        </wsse:BinarySecurityToken>
        
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <!-- Digital signature components -->
        </ds:Signature>
    </wsse:Security>
</soap:Header>

SAML Token Profile (overheidscontext)

<soap:Header>
    <wsse:Security>
        <saml:Assertion 
            xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
            ID="assertion-123"
            Version="2.0"
            IssueInstant="2024-03-05T14:30:00Z">
            
            <saml:Issuer>https://identity.overheid.nl</saml:Issuer>
            
            <saml:Subject>
                <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">
                    gemeente_amsterdam_user_001
                </saml:NameID>
            </saml:Subject>
            
            <saml:AttributeStatement>
                <saml:Attribute Name="gemeente">
                    <saml:AttributeValue>Amsterdam</saml:AttributeValue>
                </saml:Attribute>
                <saml:Attribute Name="rol">
                    <saml:AttributeValue>BAG_Beheerder</saml:AttributeValue>
                </saml:Attribute>
            </saml:AttributeStatement>
        </saml:Assertion>
    </wsse:Security>
</soap:Header>

SOAP in overheidscontext

StUF over SOAP

StUF (Standaard Uitwisseling Formaat) wordt vaak over SOAP getransporteerd:

<!-- StUF Lv01 bericht over SOAP -->
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
        <wsse:Security>
            <!-- Authentication headers -->
        </wsse:Security>
    </soap:Header>
    
    <soap:Body>
        <StUF:Lv01Bericht 
            xmlns:StUF="http://www.stufstandaarden.nl/koppelvlak/stuf"
            xmlns:BG="http://www.stufstandaarden.nl/onderlaag/bg">
            
            <StUF:stuurgegevens>
                <StUF:berichtCode>Lv01</StUF:berichtCode>
                <StUF:zender>
                    <StUF:organisatie>0363</StUF:organisatie>
                    <StUF:applicatie>BRP-Service</StUF:applicatie>
                </StUF:zender>
                <StUF:ontvanger>
                    <StUF:organisatie>0518</StUF:organisatie>
                    <StUF:applicatie>Zaak-Service</StUF:applicatie>
                </StUF:ontvanger>
            </StUF:stuurgegevens>
            
            <StUF:vraag>
                <StUF:filter>
                    <BG:BSN>123456789</BG:BSN>
                </StUF:filter>
            </StUF:vraag>
        </StUF:Lv01Bericht>
    </soap:Body>
</soap:Envelope>

Digipoort/Diginetwerk

Voor communicatie met centrale overheidsvoorzieningen:

<soap:Envelope 
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:dp="http://www.logius.nl/digipoort">
    
    <soap:Header>
        <!-- PKIoverheid certificaat -->
        <wsse:Security>
            <wsse:BinarySecurityToken>
                <!-- PKI certificaat data -->
            </wsse:BinarySecurityToken>
        </wsse:Security>
    </soap:Header>
    
    <soap:Body>
        <dp:RoutingInformation>
            <dp:BerichtSoort>BelastingAangifte</dp:BerichtSoort>
            <dp:Organisatie>12345678</dp:Organisatie>
        </dp:RoutingInformation>
        
        <dp:BerichtData>
            <!-- Actual business data -->
        </dp:BerichtData>
    </soap:Body>
</soap:Envelope>

SOAP-implementatie en tools

Server-side implementaties

Java (JAX-WS):

@WebService
public class PersoonService {
    
    @WebMethod
    public GetPersoonResponse getPersoon(GetPersoonRequest request) {
        // Validate BSN
        if (!isValidBSN(request.getBSN())) {
            SOAPException fault = new SOAPException("Ongeldig BSN");
            throw new SOAPFaultException(createFault("Client", "InvalidBSN", 
                "Het opgegeven BSN is niet geldig"));
        }
        
        // Business logic
        Persoon persoon = persoonRepository.findByBSN(request.getBSN());
        
        GetPersoonResponse response = new GetPersoonResponse();
        response.setPersoon(persoon);
        return response;
    }
    
    private boolean isValidBSN(String bsn) {
        return bsn != null && bsn.matches("[0-9]{9}");
    }
}

C# (WCF):

[ServiceContract(Namespace = "http://example.org/persoon-service")]
public interface IPersoonService
{
    [OperationContract]
    [FaultContract(typeof(ValidationFault))]
    GetPersoonResponse GetPersoon(GetPersoonRequest request);
}

public class PersoonService : IPersoonService
{
    public GetPersoonResponse GetPersoon(GetPersoonRequest request)
    {
        // Validation
        if (!IsValidBSN(request.BSN))
        {
            var fault = new ValidationFault 
            { 
                Message = "Ongeldig BSN format",
                Field = "BSN" 
            };
            throw new FaultException<ValidationFault>(fault);
        }
        
        // Business logic
        var persoon = _persoonRepository.GetByBSN(request.BSN);
        return new GetPersoonResponse { Persoon = persoon };
    }
}

Client-side consumption

Python (zeep library):

from zeep import Client
from zeep.wsse.username import UsernameToken

# Create client with authentication
wsdl_url = 'https://api.gemeente.nl/soap/persoon-service?wsdl'
client = Client(wsdl_url, wsse=UsernameToken('username', 'password'))

# Call service
try:
    response = client.service.GetPersoon(BSN='123456789')
    print(f"Gevonden: {response.Persoon.Voornaam} {response.Persoon.Achternaam}")
    
except Exception as e:
    print(f"SOAP Fault: {e}")

PHP (SoapClient):

<?php
$wsdl = 'https://api.gemeente.nl/soap/persoon-service?wsdl';
$options = [
    'login' => 'username',
    'password' => 'password',
    'soap_version' => SOAP_1_2,
    'trace' => true,
    'exceptions' => true
];

try {
    $client = new SoapClient($wsdl, $options);
    
    $request = ['BSN' => '123456789'];
    $response = $client->GetPersoon($request);
    
    echo "Gevonden: " . $response->Persoon->Voornaam;
    
} catch (SoapFault $fault) {
    echo "SOAP Fault: " . $fault->getMessage();
    echo "Request: " . $client->__getLastRequest();
}
?>

SOAP vs REST vergelijking

Wanneer SOAP kiezen:

✅ SOAP-voordelen:

  • Enterprise security: Uitgebreide WS-Security standaarden
  • ACID transactions: Database-like transaction support
  • Formal contracts: WSDL biedt strikte interface-definitie
  • Built-in error handling: Gestandaardiseerde fault-structuur
  • Legacy integration: Veel enterprise-systemen zijn SOAP-based

✅ SOAP use cases:

  • Financial transactions (banktransacties)
  • Government-to-government communication
  • Mission-critical systems waar ACID-properties crucial zijn
  • Integration met .NET/Java enterprise-stacks
  • Compliance-vereisten die WS-Security mandateren

Wanneer REST preferen:

⚠️ REST-voordelen:

  • Simplicity: Makkelijker te implementeren en debuggen
  • Performance: Minder overhead, snellere processing
  • Web-friendly: Native HTTP-verbs, cacheable
  • Mobile-friendly: Lightweight, JSON-based
  • Developer experience: Intuitive API design

⚠️ REST use cases:

  • Public APIs voor third-party developers
  • Web/mobile applications
  • Microservices-architectuur
  • Real-time/streaming data
  • Rapid prototyping

SOAP Best practices voor overheid

Error handling

<!-- Structured error response -->
<soap:Fault>
    <soap:Code>
        <soap:Value>soap:Client</soap:Value>
        <soap:Subcode>
            <soap:Value>gov:ValidationError</soap:Value>
        </soap:Subcode>
    </soap:Code>
    <soap:Reason>
        <soap:Text xml:lang="nl">Gegevens validatie gefaald</soap:Text>
    </soap:Reason>
    <soap:Detail>
        <gov:ErrorDetails>
            <gov:ErrorCode>BSN_INVALID</gov:ErrorCode>
            <gov:Field>BSN</gov:Field>
            <gov:Description>BSN moet 9 cijfers bevatten</gov:Description>
            <gov:HelpUrl>https://docs.gemeente.nl/errors/BSN_INVALID</gov:HelpUrl>
        </gov:ErrorDetails>
    </soap:Detail>
</soap:Fault>

Logging en monitoring

<!-- Custom headers voor tracing -->
<soap:Header>
    <gov:RequestMetadata>
        <gov:RequestId>req_abc123</gov:RequestId>
        <gov:Timestamp>2024-03-05T14:30:00Z</gov:Timestamp>
        <gov:OrganisationCode>0363</gov:OrganisationCode>
        <gov:ApplicationName>BRP-Console</gov:ApplicationName>
        <gov:UserContext>
            <gov:UserId>user.gemeente@amsterdam.nl</gov:UserId>
            <gov:Role>BAG_Reader</gov:Role>
        </gov:UserContext>
    </gov:RequestMetadata>
</soap:Header>

Performance optimization

// Connection pooling
@Bean
public HttpComponentsClientHttpRequestFactory httpRequestFactory() {
    HttpComponentsClientHttpRequestFactory factory = 
        new HttpComponentsClientHttpRequestFactory();
    factory.setConnectionTimeout(5000);
    factory.setReadTimeout(30000);
    
    // Connection pooling
    PoolingHttpClientConnectionManager connManager = 
        new PoolingHttpClientConnectionManager();
    connManager.setMaxTotal(100);
    connManager.setDefaultMaxPerRoute(20);
    
    HttpClient httpClient = HttpClientBuilder.create()
        .setConnectionManager(connManager)
        .build();
    factory.setHttpClient(httpClient);
    
    return factory;
}

SOAP-migratie naar REST

Voor organisaties die van SOAP naar REST willen migreren:

Gefaseerde migration

Fase 1: Wrapper-layer

@RestController
public class PersoonRestController {
    
    @Autowired
    private PersoonSoapClient soapClient;
    
    @GetMapping("/api/personen/{bsn}")
    public ResponseEntity<PersoonDTO> getPersoon(@PathVariable String bsn) {
        try {
            // Call existing SOAP service
            GetPersoonResponse soapResponse = soapClient.getPersoon(bsn);
            
            // Transform to REST DTO
            PersoonDTO restDTO = PersoonMapper.toDTO(soapResponse.getPersoon());
            
            return ResponseEntity.ok(restDTO);
            
        } catch (SOAPFaultException e) {
            return ResponseEntity.badRequest()
                .body(ErrorMapper.toRestError(e));
        }
    }
}

Fase 2: Parallel implementations

  • SOAP en REST endpoints parallel beschikbaar
  • Gradual migration van clients
  • A/B testing voor performance/reliability

Fase 3: SOAP deprecation

  • Clear communication naar consumers
  • Sunset headers in SOAP responses
  • Monitoring van SOAP usage

SOAP blijft een belangrijk protocol in overheidscontext, vooral voor mission-critical, secure communicatie tussen systemen. Hoewel REST dominanter wordt voor nieuwe development, is SOAP-kennis essentieel voor het onderhouden en migreren van bestaande enterprise-systemen.

Resources: