Wowza Module Development with automated delivery pipelines
Working on modules for Wowza Streaming Engine™ is part of our daily job at Raskenlund. Getting builds integrated into our cloud-based CI/CD pipelines required extra efforts to make them compatible with modern industry standards. This article tells the story of how we achieved this while delivering streaming solutions to our customers.
The standard module development approach advertised by Wowza in their tutorials is a great starting point for absolute beginners to Java. It gives them a rapid start with the experience of rapid deployments to a co-located Wowza Streaming Engine installation. We found that this approach is not aligned to our vision for three major reasons:
- Relying on the local installation of WSE is too much overhead for lightweight CI builds, it practically makes builds non-portable
- Lack of dependency resolution can cause problems when the module’s own open-source library dependencies clash or overlap with the dependencies of the installed WSE version itself
- Apache Ant as a build tool is on sunset and is not considered a strategical build tooling by the industry
We decided to go for a Gradle build process that can be easily executed on popular cloud-based continuous integration services.
Gradle is a very popular build tool that uses a declarative domain-specific language expressed in Groovy or Kotlin. It stands somewhere on the middle ground between Ant and Maven: you get the flexibility of ad-hoc scripting wherever needed in the build process, but it has convention-over-configuration approach and features a sophisticated dependency resolution system compatible with Maven repositories.
Solving the dependency problem
A vanilla installation of Wowza Streaming Engine 4.8.17 drops nearly 140 individual jar files into the lib folder which all will be on the classpath when your module is loaded. Our commercial modules have to be tested and made compatible with a range of WSE versions. Wowza release notes might or might not mention version changes in the dependencies, only bigger, breaking changes (e.g. log4j 1.x to 2.x) are expected to be highlighted.
The installed jar files fall into two categories: the API of WSE itself (wse-*.jar) and third-party dependencies. In the beginning, we tried to keep track of the versions of certain libraries that our modules were using, to make sure they are not incompatible with the ones shipped with Wowza itself. Some of the libraries that our modules often share with Wowza are: Google Gson, Apache HttpClient, AWS SDK, etc. We had to branch our code based on which Wowza version they were compatible with, and keeping these branches on the same feature level was an administration overhead.
We wanted to specify the Wowza version that we are building and testing our modules with, as a parameter in our build, expecting Gradle to bring in the right set of WSE SDK jar files and the right set of accompanying dependencies at the same time.
To achieve this we set up a private Maven repository and started to discover and publish the dependencies in it as a bill-of-material (“BOM”), similarly to how it is done in the open-source Spring Boot project. To automate this publication, we wrote a simple Groovy script that we execute against every new release of Wowza Streaming Engine shortly after the announcement. The script publishes the wse-*.jar files into our Maven repository under the group identifier com.wowza.
As a second step, we scan and identify individual third-party open source dependencies and identify them in the public Maven repository based on the jar file SHA1 checksums that we can calculate for each file. Within a minute as the result of the scan, we produce a list of canonical maven coordinates that we can put into a BOM. There is a small set of known “outlier jar” files that are not present in Maven central and we deal with them separately.
The produced BOM file is also published to our Maven repository, making it consumable for CI builds. Besides the pom.xml files we also generate Gradle module files that can keep track of additional information, such as the Java version used by WSE in (e.g.”org.gradle.jvm.version”: 9) attribute.
As an end result, we can compile our modules against an arbitrary version of WSE by simply putting the below few lines in our build.gradle file and the code just compiles on our CI server:
gradle.properties: wowzaVersion=4.8.17
build.gradle: dependencies { implementation group: 'com.wowza', name: 'wowza-bom', version: wowzaVersion }
Shared and shaded dependencies
If our modules use a third-party dependency, such as Gson for working with JSON payloads, importing the Wowza BOM provides it to the Java compiler and when the module is deployed into the server, the required classes will be available on the classpath. This is relatively simple and safe to rely on in most cases but can lead to incompatibilities with future versions if at some point in time Wowza ships with a newer version of the library that has incompatible changes. Those would break our modules resulting in unpredictable runtime exceptions.
Application servers and servlet containers have gone a long way to provide classloader isolation to hosted applications and allow different versions of libraries to coexist using separate classloaders. This has not been a priority in a media-focused server. We decided to use the shadow technique to repackage dependencies under a different Java package name which provides isolation even if additional third-party modules are installed that ship with clashing transitive dependencies.
Configuring the shadowJar plugin in Gradle is fairly easy:
plugins { id 'java' id 'com.github.johnrengelman.shadow' version '6.1.0' }
dependencies { shadow group: 'com.wowza', name: 'wowza-bom', version: wowzaVersion implementation 'org.apache.commons:commons-text:1.9' implementation 'com.raskenlund:wse-modules-common:1.1.1' testImplementation(platform('org.junit:junit-bom:5.7.0')) testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core:3.18.1') } shadowJar { relocate 'org.apache.commons.text', 'com.raskenlund.shadow.caption.text' }
In the above example the wowza-bom dependency is put into the shadow configuration because we don’t want to package it into our output jar file. The commons-text dependency is very popular and is quite probable that it would be used in other modules, so the safest thing to do is relocating it under a different Java package name. We also embed our own common code library into the output jar to make the final product a single, self-contained “fat jar”.
Summary
By filling in the gap of undefined dependencies, we were able to set up a professional delivery pipeline that allows us to deliver changes from pull requests to deployable artifacts going through the necessary steps of compilation, automated testing, packaging in a WSE agnostic cloud environment. Targeting different versions of Wowza Streaming Engine is a now a parameter of our CI pipeline and makes our work more efficient.