Trifork Blog

Axon Framework, DDD, Microservices

Trifork implements Overstapservice Onderwijs (OSO)

December 4th, 2012 by
|

Trifork is currently busy implementing the Overstapservice Onderwijs (OSO) Traffic Center, a system that mediates the transfer of student files between schools. The Traffic Center standardizes the exchange of student files between primary- and secondary schools. The OSO project is a continuation of the Electronisch Leerling Dossier (ELD) project. It enables the digital transfer of student related documents between the individual student administration systems (LAS) of schools, regional initiatives and other stakeholders. A major challenge for the Traffic Center is that by law it is not allowed to store the student files, but it is purely facilitating the exchange.

In this blog entry I provide some insights into how the exchange is eventually facilitated and some of the challenges we had with it.

Exchanging a document

The ELD, and later OSO, Traffic Center are the result of an extensive process in which many organizations were involved. This process has led to a specification of how the interchange of documents related to a student should work. A big part of this is the definition of a SOAP interface that the Traffic Center has to implement and offer. These services are depicted in the diagram below.

The Traffic Center is designed as a secured SOAP service. Participating systems are authenticated with client certificates that they get from the Certificate Authority located in OfficeHeart, the back-end system that also contains the register of all participants. This register is synchronized with the Traffic Center.

To become active participants, schools and other organizations have to go through a qualification process. When they become active, they register the URLs of their systems with the Traffic Center. Participants signal their intention to exchange a document with other parties to the Traffic Center.

The Traffic Center verifies if participants in an intended transfer are able and allowed to exchange documents. The Traffic Center then returns a unique session token and the URL of the intended target. In this way, it acts as a service locator.

Having received the URL, participants access the other party directly to request a document. This document is returned after a verification call for the session to the Traffic Center. After having received the document, the initiating party closes the session by signalling the receipt of the document to the Traffic Center.

An audit log of the exchange is synchronized back to OfficeHeart to assist customer support.

Technology

As we have a long history with SpringSource products, Trifork uses Spring WS, the tried and tested Web Services framework.

Maybe more surprising is the use of Groovy and Grails instead of Java to tie it all together. Grails, as you may know, is a framework for building web applications inspired by Ruby on Rails. It leverages the “convention over configuration” paradigm, which tries to minimize the amount of work for developers by generating conventional aspects of the application while leaving only unconventional aspects to be configured or implemented by the developer.

Generally, Grails is associated with generating mainly CRUD-like applications. Although there is no graphical user interface in the traffic center, using Grails still has a lot advantages in this case:

  • Grails was used to model the entities in the application, thereby (almost) automatically gaining a REST interface for OfficeHeart to use for synchronizing the list of active participants.
  • Spring WS, which was used for the SOAP interface, uses the “contract first” approach to web services. The reasons for this are explained in this section of the Spring WS reference manual. In this case, the Groovy XmlSlurper is used to parse the SOAP messages while the XmlMarkupBuilder DSL is used to create the SOAP response messages. The match of Spring WS and the Groovy XML tools results in compact, very readable code.
@PayloadRoot(namespace = OverstapServiceEndpoint.NS_OVER, localPart = 'opvragenRegionaleInitiatievenRequest')
@ResponsePayload
public Element handleOpvragenRegionaleInitiatievenRequest(@RequestPayload GPathResult payloadRoot, MessageContext messageContext) {

   log.debug("received opvragenRegionaleInitiatievenRequest message")

   def brin = payloadRoot.'over:schoolId'.text()
   def levertOutput
   if (payloadRoot.'over:levertOutput'.text()) {
      levertOutput = payloadRoot.'over:levertOutput'.text() as Boolean
   }

   doValidateActor(messageContext, brin)

   def writer = new StringWriter()
   def xml = new MarkupBuilder(writer)

   try {
      def resultaatValue = overstapService.opvragenRegionaleInitiatieven(brin, levertOutput)

      log.debug("Registration result: ${resultaatValue}")
      xml.'over:opvragenRegionaleInitiatievenResponse'('xmlns:over': NS_OVER) {
         resultaatValue.each() { ri ->
            'over:regionaalInitiatief' {
               'over:regionaalInitiatiefId'(ri.brin)
               'over:naam'(ri.naam)
               'over:levertOutput'(ri.levertOutput)
            }
         }
      }
   } catch(ELDException e) {
      xml.'over:opvragenRegionaleInitiatievenResponse'('xmlns:over': NS_OVER) {
         'over:fout'(e.resultaat)
      }
   }
   return toElement(writer)
}
def "Start document exchange - Exchange already started ('OverdrachtReedsActief')"() {
   given:
      def xml = """
         <over:overdrachtRequest ${PAYLOAD_NAMESPACE_DECL}>
            <over:overdracht>
               <over:bronBrin>${huidigeDeelnemer.brin}</over:bronBrin>
               <over:doelBrin>${nieuwBrin}</over:doelBrin>
               <over:zoeksleutel>${personalKey}</over:zoeksleutel>
               <over:overdrachtsoort>overstapdossier</over:overdrachtsoort>
            </over:overdracht>
         </over:overdrachtRequest>
      """
      def element = toGPathResult(xml)
      messageContext.getProperty(OverstapServiceEndpoint.ACTOR_SUBJECTDN) >> { return brinPrefix+nieuwBrin}
      overstapService.startOverdracht(OverdrachtSoort.OVERSTAPDOSSIER, huidigeDeelnemer.brin, nieuweDeelnemer.brin, personalKey) >>
         {throw new ELDException(Resultaat.OverdrachtReedsActief)}
   when:
      def result = endpoint.handleOverdrachtRequest(element, messageContext)
      def raw = toRawXml(result)
   then:
      raw.contains('OverdrachtReedsActief')
}
  • a productivity win through Groovy closures and other syntax sugar, especially for manipulating collections:

def registeredLasCodes = documentBron.lasUrls*.code

  • maintainability because of the inherently much smaller code base

All in all, we were able to implement the application in less than 1500 lines of code and a SIG maintainability score of +2.

Trifork activities

Just to summarize what we did here it is:

  • Trifork implemented the Traffic Center and integrated it with OfficeHeart.
  • An automated functional test suite was set up to guard against regression.
  • The acceptance, qualification and production platforms were set up and delivered to KennisNet.
  • Vendors of student administration systems were assisted during integration testing.
  • Currently, Trifork develops and manages the technical specifications documents that are sent out to software vendors that want to support the OSO standard.

One Response

  1. December 4, 2012 at 13:48 by Hans Westerbeek (@hwstrbk)

    Wow, such clean and clever code 🙂