Working with SOAP often gets tricky, and dealing with WSDL might be a huge contribution to the complexity of this task. Really, it could be the least expected thing to face when you are into a modern & fancy language like for example, Scala, that is well known for its reactiveness and asynchronous way of dealing with requests. In fact, many of the software developers that have made their way into industry quite recently, might not even know about SOAP and WSDL protocols, and get quickly annoyed or even enraged when first trying to connect to such a legacy service. So, should we deprecate this altogether in favour of modern technology stack, or maybe there is a less painful solution?
SOAP: Legacy
It’s hard to argue that this SOAP thing sounds quite outdated in these days, especially in contrast with the current state of the technology. Writing a WSDL client from scratch with Kotlin, Scala or other modern language could be a pain, and lack of proper documentation for it doesn’t make life easier. But I have good news for you, there is a spot of light in the dark SOAP kingdom. Well, actually WSDL itself is the one. Despite being heavyweight and somewhat ugly, it has a certain advantage. The excessiveness of WSDL format makes it quite easy generating the client (and also server) code, maybe not for humans but definitely for automated systems.
Even compared to modern API specifications, it could actually stay on par with OpenAPI or fancy Swagger API concepts where everything is described in a language-agnostic spec. This enables huge opportunities for interoperability between different platforms and languages, down to the level of implementation. For example, if one exposes let’s say a .NET web service with WSDL spec, another could automatically generate a JVM-based client to connect to it with little to no pain of data formats conversion or incompatibility.
WSDL Import Magic
Let’s spin it further and talk about automated code generation. You might be surprised, but most enterprise-ish platforms, mainly Java and .NET, come with WSDL code generating tools out of the box. For example, there is wsimport that comes as a part of a JDK distribution. Such tools are quite powerful and should cover an auto-generation task end to end. The only remaining part is to connect your business logic to the client code and make use of it.
So, since we are on the Scala theme currently, let’s look deeper into Java’s wsimport
tool:
wsimport -p stockquote http://stockquote.example.com/quote?wsdl
The command takes a WSDL schema as a required parameter, and basically it’s just enough to produce a whole set of POJOs and interfaces, that are marked with all proper annotations. The latter ones actually do the trick: this is essentially what makes all things possible. When executed, JVM wires your client code together with internal web service client implementation, that comes out of the box, so you don’t need to bother much about low-level networking & IO. Rest of the business is to handle ins and outs properly, and be careful with errors and exceptions.
Bring Automation to the Next Level with SBT
Alright, it’s time for some hands-on. Imagine we have some SOAP web services that we need to connect too, and they expose WSDL. I deliberately took some for testing, for the sake of science & education only, of course. Run the code generator:
wsimport -s ../src/main/java -extension -p your.package.wsdl.nl -XadditionalHeaders -Xnocompile http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL
It produces a number of raw Java code in the output folder. We could proceed with connecting our business logic, as suggested above. But wait a second, what if the server side changes — we will be aware of it only at the moment of actual code execution (or on the integration tests failure moment, if we have some). Not pretty. It quickly gets not pretty at all if you will think of committing all this boilerplate Java bean code into your pristine Scala repository.
Of course, it would be way nicer to generate all that stuff automatically and keep things lean & clean. A first step for this would be to automate getting all WSDL classes with one command and make a Shell script out of this. I actually made one for you so you can have a look: wsdl_import.sh.
Then we could just wrap it with a build task: let’s take SBT as an example, since we are on Scala, so something like this should work:
lazy val wsdlImport = TaskKey[Unit]("wsdlImport", "Generates Java classes from WSDL")
wsdlImport := {
val wsdlSources = "./wsdl/src/main/java"
val d = file(wsdlSources)
if (d.isDirectory) {
// don't forget to rename to your fav one in line with WSDL generating sh
val gen = file(s"$wsdlSources/github/sainnr/wsdl")
if (!gen.exists() || gen.listFiles().isEmpty) {
import sys.process._
println("[wsdl_import] Importing Java beans from WSDL...")
"./wsdl/bin/wsdl_import.sh" !
} else
println("[wsdl_import] Looks like WSDL is already imported, skipping.")
} else
println(s"[wsdl_import] Make sure the directory ${d.absolutePath} exists.")
}
Now, we need to make sure we have all this code before Scala part compiles, for obvious reasons. Easy-peasy, we have SBT so we just need to execute the Shell script as an SBT task like above and run things in the right order, correct? Well, it’s a bit more complicated in real life. Without getting into much of the details of how SBT works, things get much easier if we separate this WSDL-Java part into a self-containing sub-project, and make a proper dependency in the master SBT configuration.
lazy val wsdl = (project in file("wsdl"))
.settings (
publishSettings,
sources in (Compile, doc) := Seq.empty
)
lazy val root = (project in file("."))
.aggregate(wsdl)
.dependsOn(wsdl)
When you compile the master project, SBT first ensures the sub-project is already compiled. But there is a catch: when you have just checked out your repository, you may not have executed the compilation. So when you first open it in the editor, some of the dependencies will be missing, of course. Hopefully, the only thing you need is to run an sbt compilecommand and, perhaps, refresh the project in IDE.
There might be another caveat if you are running your Scala application as a stand-alone client or in a lean web container (e.g. Netty if you are using Play Framework). In this case, it’s very likely the application runtime will be missing the implementation bit that helps JVM to do the SOAP magic for you, thanks to modern JRE versions and Java Jigsaw project. No need to panic though, just add a few libraries to your dependency list, or throw a single rt.jar from your JRE distribution as an unmanaged dependency:
unmanagedJars in Test += Attributed.blank(
file(System.getenv("JAVA_HOME") + "/jre/lib")
)
As a Conclusion
Alright, as a recap: we have learnt a bit about SOAP and WSDL and hopefully realised it’s not such a nightmare to work with, thanks to all these code generators and excessive WSDL spec. We also figured out how to automate a dirty job and found a way to keep our repositories pristine and clean from unwanted boilerplate code. It took some knowledge of SBT to configure compilation order & dependencies properly, but after all, it should work quite smoothly. To simplify things further, I made a small bootstrap template that should help you kick-start a project next time: https://github.com/sainnr/sbt-scala-wsdl-template. Hope you enjoyed this little back-to-the-past journey!
References
Please drop me a message if you see any typos or mistakes.
This article was originally published in my blog fullstackme.co.uk with little modifications.