Documentation

2 Application environment

This section dives into the basic framework of Oskari and further into the technical perspectives of Oskari's frontend and backend.

2.1 Framework

An Oskari-based web application consists of frontend code for browser-based user interface and backend functionalities that are run on the server. The user interface is implemented in JavaScript and the server functionality in Java. Both, the frontend and the backend, are built with extensibility in mind.

An example for building your own service is provided as a template application that can be run as is and it enables an easy starting point for customizing the service.

The picture below shows the generic idea of Oskari.

application-environment-1.png

2.2 Frontend

The user interface for Oskari-based services usually is a Javascript-based single-page app. The UI is built by selecting a series of bundles that provide functionalities/capabilities for an application. You can mix and match the bundles or create new ones to customize the application for your needs.

Bundles are used as uniform containers to ship and share new functionality to the application setups. Additions to an existing functionality are implemented as plugins shipped within the bundles.

A bundle can work "as is" for providing its functionality with its own user interface and/or it can provide a documented API that can be used to interact with the functionality programmatically. One example of a bundle that doesn't have an UI itself would be a bundle called drawtools that only provides an API that is used by measurement tools, my places functionality and others that has the user "draw" something on the map. The API also helps implementing a customized drop-in replacements for functionalities when required.

2.2.1 Frontend architecture

An Oskari-based frontend application includes the Oskari framework code and a selection of bundles that implement functionalities for the application. Bundles have a lifecycle and are started in sequence. Bundles can communicate with each other using events, requests and services. The framework code of Oskari provides the messaging system for events, requests and service registry but also an API which bundles need to implement so they can be included in an Oskari based application like having lifecycle functions/a starting point that can be called when the functionality is started.

The sequence diagram below explains what happens in the frontend initialization process (try reloading the page if the diagram is not rendered properly).

sequenceDiagram
  box rgb(245, 242, 222) Oskari-frontend
  participant Oskari as Oskari
  participant Bundle as Bundle
  end
  box rgb(163, 196, 188) Oskari-server
  participant Server as Server
  end

  Oskari ->> Oskari: Oskari.app.startApplication()
  Oskari ->> Server: GetAppSetup (UUID)
  Server -->> Oskari: app definition
  loop For each bundle
    Oskari ->> Bundle: inject configuration
    Oskari ->> Bundle: start
    Bundle -> Bundle: init functionality
    Bundle ->> Oskari: bundle.started
  end
  Oskari ->> Oskari: app.started

The frontend in started by the application code calling Oskari.app.startApplication() (in applications index.js for example) which triggers a call for the server action route called GetAppSetup. The GetAppSetup response lists all the bundles that should be started for that specific application and includes the configuration and state of those bundles (like which layers are on the map and what are the coordinates for the center of the map etc). The application to start is noted by an UUID when the page is opened (usually a parameter on the page URL) but the server has several options to default an appsetup based on user role etc. The frontend then proceeds with starting the requested bundles with the included configuration in sequence until the whole application has been started. An event is triggered after each started bundle and another once the whole application has been started that enables programmatically react to such lifecycle events.

The bundle called mapfull is usually a starting point for the bundle sequence as it creates the map implementation that most functionalities expect to be present when started.

Bundle functionality

  • Bundles can provide an API for other bundles to request some operation through a request handler.
  • A bundle can provide a request class and register a handler for the request in the Oskari framework. This is refered to as the request API for the bundle and should be fairly stable.
  • Another bundle can then send the request which will be processed by the other bundle.
  • Another way to communicate with other bundles is to send out an event through Oskari framework.
  • Any bundle registered as an eventlistener for the given event is then notified about the event.

2.2.2 Frontend libraries and technologies

Oskari frontend uses the following libraries and technologies (for details see package.json on the oskari-frontend repository):

  • OpenLayers (map implementation)
  • jQuery (older UI implementations, migrating towards React)
  • React (current UI implementations)
  • Ant Design (UI components and icons)
  • CesiumJS (3d map implementation)
  • Lo-Dash
  • geostats.js
  • D3.js

You can get a list of licenses for all the libraries with npm, for example by running:

npx license-checker

Just to get summary of licenses you can add --summary after the command:

npx license-checker --summary

2.2.3 Frontend source code and folder structure

The frontend for Oskari-based applications can be divided into two (or more) parts:

  • the application that can be customized for a specific need
  • oskari-frontend that provides the frontend framework, built-in UI-component library and bundles that can be used as building blocks when creating applications.
  • you can also use bundles from oskari-frontend-contrib repository like ones from oskari-frontend and/or another third party repository

You can find an Oskari-based sample application source code here.

The sample application frontend source code has the following folder structure:

/applications - References to bundles that will be used in a specific application
/bundles - Implementation for application specific bundles

The applications folder has for example geoportal and embedded as applications as both of these use different set of bundles. For the sample-application the geoportal is what you expect to see when opening the Oskari application and lists all the bundles that will be used on different geoportal views on the example application (https://demo.oskari.org etc). The embedded application references the bundles that users can select to be included when publishing maps from the geoportal. We need to reference any bundle that could be started on an embedded map, but we can still optimize by using the oskari-bundle loader for ones that are always used (like the map) and use oskari-lazy-bundle to reference ones that see less use (like thematic maps). This way the file that end-users need to download when opening the map only has the ones that are common to get a smaller file size while also enabling users to use the more specific functionalities that are only loaded when used on the embedded maps.

Applications can and usually do use many of the bundles provided in oskari-frontend. You can find a list of these bundles in the bundle documentation. If you want to learn more about about oskari-frontend in general, you will find documentation for it under the "Frontend framework" section of the documentation.

2.3 Frontend bundle implementation convention

The folder structure follows a pattern where the first folder under the base bundles folder is a namespace folder. Application specific bundles can choose to skip the namespace especially if all bundles of the application would be under the same namespace. Oskari uses framework, mapping and admin namespace for most bundles. The next folder is named after the <bundle-identifier>.

Any events, requests (and request handlers) and services a bundle implements should be separated into subfolders under the bundles implementation. However this is a convention, not a requirement for application specific bundles. In addition if you have divided the code into views or components that are shown on the flyout, you can create subfolders for them as well. Having an index.js file as the starting point for bundle definition is a nice way of shortening the reference to the bundle in applications main.js file.

There is a functional requirement when using the oskari-bundle loader to load your bundle that localization files should be under resources/locale folder relative to the index.js (bundle definition/instance factory file). Usually the files are named after the language code for the localization, but this is not a functional requirement. The contents of the file declares the locale for localization.

${your root dir}
   |--bundles
      |--${namespace}
         |--${bundle-identifier}
            |--component
            |  |--MyComponent.js
            |--event
            |  |--MyEvent.js
            |--request
            |  |--MyRequest.js
            |  |--MyRequestHandler.js
            |--resources
            |  |--locale
            |    |--en.js
            |    |--fi.js
            |    |--sv.js
            |--service
            |  |--MyService.js
            |--view
            |  |--MyLoggedInView.js
            |  |--MyGuestView.js
            |--index.js
            |--instance.js
            |--Flyout.js

2.4 Backend

Backend functionality of the platform is implemented with Controllers from Spring framework and can be easily extended to handle new functionality. Also the Spring-layer is very light on top and could be substituted with another if needed.

The server-side codebase can be divided into two parts:

  • the application that can be customized for a specific need
  • oskari-server as library of maven modules

2.4.1 Backend architecture

The Java webapp archive (war-file) for Oskari server is packaged as oskari-map.war in sample-server-extension (the webapp-map module).

It handles most of the server side functionality alone, but doesn't need to include much code as it uses the Maven modules from oskari-server that handle most of the things it needs.

You can find Oskari-server source code here.

The webapp is extensible and you can add more modules from oskari-server or remove ones you don't need to adjust the functionalities your app requires. It is also very easy to add more handlers/controllers for any application specific needs when creating your own geoportal/web mapping application.

The server application template has Maven modules for an example setup with:

  • app-resources (has initial database data and migrations for the application)
  • app-specific-code (has a "Hello World" request/action handler as an example of app specific code)
  • webapp-map (uses the other two modules and packages everything up in a Java war-file)

The backend architecture in oskari-server Maven-modules can be divided into three layers: service layer, control layer and interface layer:

  1. The interface layer is very light with Spring framework Controllers for handling requests and can be easily substituted to run as portlets or similar.
  2. The controllers pass concrete HTTP-requests on to Oskari control-modules that can further process the requests and write responses.
  3. Services are used by the control-modules to handle business-logic. The service-modules could (in theory) be used in any Java-based software as libraries.

Oskari uses a concept of "action route" for processing requests made by the frontend application. Requests for action routes are processed like this:

flowchart TD
    A>User] -->|Opens page| S
    X>User] -->|Makes a search| S
    PU>User] -->|Publishes a map| S
    S[ActionRouteController] --> C{ActionRoute}
    C -->|Load page| D[GetAppSetupHandler]
    C -->|Publish a map| P[AppSetupHandler]
    C -->|Search results| E[SearchHandler]
    C -->|Application specific action| F[Your code]
    D -->|Load appsetup| AppSetupService(AppSetupService)
    P -->|Create appsetup| AppSetupService(AppSetupService)
    E -->|Search| SS(SearchService)
    SS --> |Search|OpenStreetMap>OpenStreetMap]
    SS --> |Search|WFS-service>WFS-service]
    AppSetupService --> dbId[("oskaridb")]

Service layer

Service modules should be common libraries usable in any application. The actual business logic for Oskari operations should be in these modules.

  • service-base has some common helpers and domain classes which are used throughout Oskari backend
  • service-permission is a generic authorization lib for deciding who gets to see/do what
  • service-search is a generic search lib that can be extended by adding and registering search channels.
  • service-map has most (maybe a bit too much) of the business logic for servicing the Oskari functionalities
  • service-control defines the control/routing structures/interfaces for control-layer to build upon
  • shared-test-resources has some common helpers/templates to help testing

Control layer

Control modules build on top of the service layer.

  • control-base is the basis for all control-modules and has most of the basic request handlers needed by the Oskari frontend.
    • NOTE! control-base contains some very specific functionalities that should be separated into separate control-extensions (for example thematic maps support)
  • control-myplaces provides funtionality related to myplaces functionality.
  • control-example provides example implementations for functionalities required by Oskari but usually overridden by platform specific functionalities such content management for user guide etc.
  • content-resources has tools, templates and scripts for populating and migrating the database

Functions:

  • Handles requests made by Oskari frontend
  • Parses request parameters for input values to be used on service invocations
  • calls one or more services to do business logic
  • format a response based on service response

Interface layer

The interface-modules build on top of the control-modules. Basically an HTTP interface with reference implementations for:

  • HTTP Servlet: oskari-server/servlet-map
  • Webapp: sample-server-extension/webapp-map

Responsible for:

  • Handling user sessions
  • Generating an ActionParameters object based on incoming request abstracting/normalizing the request for control layer
  • Forwarding the request to control layer

2.4.2 Server-side libraries and technologies

Oskari backend uses the following libraries and technologies:

  • Tomcat/Jetty or similar as Java servlet container
  • PostgreSQL (database)
  • PostGIS (spatial data extension for PostgreSQL)
  • Redis (for caching and communication in clustered server environment)

Based on your needs you can decorate your architecture by adding components like these in front of the Oskari server:

  • HAProxy (proxy, load balancer)
  • Apache HTTPD (proxy, load balancer)
  • Nginx (proxy, load balancer)
  • F5 load balancer

Having HTTPD or nginx for serving the static frontend application and passing other requests to Tomcat/Jetty is a popular choice.

2.4.3 Server-side source code

You can find Oskari backend source code in here.

Note that oskari-server doesn't have a runnable webapp to reduce forking as webapps are usually (heavily) customized per application requirements.

You can find a template to start your server customization with our example template here.