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.
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:
- The interface layer is very light with Spring framework Controllers for handling requests and can be easily substituted to run as portlets or similar.
- The controllers pass concrete HTTP-requests on to Oskari control-modules that can further process the requests and write responses.
- 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.