Go and wash your mouth with SOAP

It’s been a long while since I posted something with any amount of code, so I thought I’d try my hand at it again.

I put to words what I thought about , and I’m pleased to say I things have improved markedly – to the point where I found myself using it of late.

Besides the strong points I outlined earlier, it’s matured beyond the shortcomings I came across, with a few extras rolled into the bargain:

  • Stupendous ease in cross-compiling1
  • Noticeable improvements in performance
  • More (battle-tested) libraries

It’s not, however, a breathtaking language, or one that I feel comfortable using when a modicum of abstraction is required. Even with first-order functions and fairly flexible maps, it’s hard to achieve the kind of lofty high-level judo moves that – in fact, most of them are impossible by design, which may or may not be your cup of tea.

In a nutshell, and drawing a parallel with “conventional” IT shop , feels like a blue collar language that you can build stuff on and maintain according to broadly consensual, standardized patterns. “Coding in the large”, right?

Although I still find it somewhat horrific to fish dependencies directly out of Github and Launchpad as a matter of course, the terse, focused tooling and mostly uniform formatting make it easy to get into. So much so, in fact, that after you get used to its particular idioms it’s almost boring in its straightforwardness.

So I tried using it to do really boring stuff – stuff that I’ve done in , and , and that I hate with a passion like the fire of a thousand suns:

I used it to invoke a SOAP service on our Enterprise Service Bus.

This is a sort of masochistic kata I invariably end up doing in every programming language I use, partly because I have to and partly because it’s the kind of real-life problem that hipster developers underestimate. It also usually requires you to bang your head repeatedly against a number of walls before it works properly, which is still considered (by and large) to be a valid, character-forming approach to education.

SOAP isn’t something does out of the box2, so I had to dig a fair bit in order to figure out how to achieve what I wanted. As far as I know, and given ’s focus on more modern approaches to web services, there isn’t any “best practice” on how to go about doing this (if there is, by all means write about it someplace, like I’m doing now).

For , I have a staple solution that serves me very well – pysimplesoap, to which I contributed in the past, and which will parse just about any WSDL file you throw at it and dynamically create any number of proxy classes, readily accessible through late binding. It’s terrifying and wonderful and profoundly disturbing all at the same time, but works extremely well and (after it builds all the proxies) plenty fast enough.

For , there’s wsimport – which I use with / interop – or heavy-handed approaches like “The Axis Of Pain” and other enterprisey libraries. All told, your mileage may vary, but it’s usually quite long.

But let’s get back to . I picked a service I know very well – I won’t tell you what it is exactly, but let’s call it QueryEntity for the sake of argument (as , names were changed to protect the guilty).

Here’s a typical QueryEntity request:

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:envelope="http://schemas.xmlsoap.org/soap/envelope/" envelope:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <Header xmlns="http://schemas.xmlsoap.org/soap/envelope/">
        <ESBCredentials xmlns="http://esb/definitions">
            <ESBUsername xmlns="http://esb/definitions">user@esb</ESBUsername>
            <ESBPassword xmlns="http://esb/definitions">changeme</ESBPassword>
        </ESBCredentials>
    </Header>
    <Body xmlns="http://schemas.xmlsoap.org/soap/envelope/">
        <QueryEntity xmlns="http://esb/queryservice">
            <ClientGUID>CA55E77E-1962-0000-2014-DEC1A551F1ED</ClientGUID>
            <QueryString>wally</QueryString>
        </QueryEntity>
    </Body>
</Envelope>

For the sake of clarity, I’m using the non-namespace-prefixed tag style, and I’ve also considerably simplified the payload. You’ll notice right away that this uses header auth (the whole thing goes atop SSL anyway and the services usually support multiple auth methods like temporary tokens and whatnot, so this is actually A Good Thing).

Now, how does one go about doing this in , especially considering that you want to invoke several services with different payloads in the envelope Body?

The standard answer in is to use struct field tagging, like so:

type Envelope struct {
    XMLName       xml.Name       `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
    EncodingStyle string         `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
    Header        EnvelopeHeader `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"`
    Body          EnvelopeBody   `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}

This is what the xml/encoding package uses to generate corresponding tags for each field (via reflection). And it is fine and good until you realize that you’re going to have multiple distinct Body payloads.

It would be borderline insane to go about defining as many Envelope subtypes as SOAP services, so what do you do?

Well, you use an empty interface{}, which allows you to assign other types:

// This has to change depending on which service we invoke
type EnvelopeBody struct {
    Payload interface{} // the empty interface lets us assign any other type
}

But there’s a catch – if you just go in blindly and assign a QueryEntity struct to the Payload field , you’ll get this after you run it through the encoder:

...
    <Body xmlns="http://schemas.xmlsoap.org/soap/envelope/">
        <Payload>
            <QueryEntity>
                <ClientGUID>CA55E77E-1962-0000-2014-DEC1A551F1ED</ClientGUID>
                <QueryString>wally</QueryString>
            </QueryEntity>
        </Payload>
    </Body>
</Envelope>

…which the ESB will merrily tell you to go stuff somewhere else, because it doesn’t match the service contract.

The solution is to define your types like so:

import (
    "./esb" // the full ESBCredentials type is defined elsewhere
    ...
)

// Sample data
var ESBUsername = "user@esb"
var ESBPassword = "changeme"
var ClientGUID = "CA55E77E-1962-0000-2014-DEC1A551F1ED"

type Envelope struct {
    XMLName       xml.Name       `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
    EncodingStyle string         `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
    Header        EnvelopeHeader `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"`
    Body          EnvelopeBody   `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
}

// This is fixed independently of whatever port we call
type EnvelopeHeader struct {
    Credentials *esb.ESBCredentials `xml:"http://esb/definitions ESBCredentials"`
}

// This has to change depending on which service we invoke
type EnvelopeBody struct {
    Payload interface{} // the empty interface lets us assign any other type
}

type QueryEntity struct {
    // having an XMLName field lets you re-tag (and rename) the Payload
    XMLName       xml.Name `xml:"http://esb/queryservice QueryEntity"`
    ClientGUID   *string
    QueryString  *string
}

The XMLName field and accompanying tag is an idiomatic construct whose only purpose is to override the generation logic so that the Payload field is never mapped into the resulting .

This took me a fair while to figure out, and is the reason for the fairly lengthy example – which isn’t quite finished yet, for I’ve yet to fill in the whole thing and perform the actual request, which is done like so:

func CreateQuery(query string) *Envelope {
    // Build the envelope (this could be farmed out to another func)
    retval := &Envelope{}
    retval.EncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
    retval.Header = EnvelopeHeader{}
    retval.Header.Credentials = &esb.ESBCredentials{}
    retval.Header.Credentials.ESBUsername = &ESBUsername
    retval.Header.Credentials.ESBPassword = &ESBPassword

    // Build the payload that matches our desired SOAP port
    payload := QueryEntity{}
    payload.ClientGUID = &HouseholdId
    payload.QueryString = query

    // ...and in the darkness bind it
    retval.Body.Payload = payload
    return retval
}


func InvokePitsOfDarkness() {
    ...
    buffer := &bytes.Buffer{}
    encoder := xml.NewEncoder(buffer)
    envelope := CreateQuery("wally")
    err := encoder.Encode(envelope)
    // this is just a test, so let's panic freely
    if err != nil {
        log.Panic("Could not encode request")
    }

    client := http.Client{}
    req, err := http.NewRequest("POST", "https://esb/service", buffer)
    if err != nil {
        log.Panic(err.Error())
    }

    req.Header.Add("SOAPAction", "\"http://esb/service/QueryEntity\"")
    req.Header.Add("Content-Type", "text/xml")
    resp, err := client.Do(req)
    if err != nil {
        log.Panic(err.Error())
    }
    if resp.StatusCode != 200 {
        log.Panic(resp.Status)
    }

    result, err := GoElsewhereToDecode(resp.Body)
    ...
}

Decoding the actual result is left as an exercise to the reader – and I haven’t finished it myself for all the possible return values, but I’m in the process of learning how to use reflection to save me the trouble of setting up various kinds of structs to bind the return data to.

It bears noting that, for the moment, I’m still using to do this in “production”, since there are no noticeable benefits from switching languages at this point (and no point in doing silly benchmarks in something that doesn’t have any timing or resource constraints). But I can see myself taking that down and using instead in a few months, once I have a better feel for the language3.

Incidentally, the equivalent code in isn’t pretty either – wsimport works for me, but a “nicer” approach using clojure.data.xml didn’t go very far because it currently doesn’t know about namespaces – I had to hack those in after the fact, but if I ever find a nice, clean and reusable solution I’ll post about it.

And now, back to less technical stuff. I’ve been mulling product management and business development again, and I’m likely to have something to say on those during the upcoming weeks.


  1. Which comes in especially handy if, like me, you like to tinker with resource-constrained ARM devices, for which seems to be a natural fit – I can build a binary on my and run it directly on a development board without the usual gcc shenanigans, which makes me very happy. ↩︎

  2. Realistically, it’s not something anything ought to do out of the box, unless you belong to that group of people who believed the likes of COBOL, ASN.1 and X.400 were spiffing ideas. As far as my experience goes, shares a usage pattern with violence – even when it’s not the right solution or it plain doesn’t work, people just try to use more of it. ↩︎

  3. There’s also the matter of being anointed as suitable for inclusion in our ecosystem, but that’s another story entirely. When I joined up was a niche thing, and now every time I muster the time to deliver a talk on it the room fills to capacity… ↩︎