Skip to content

GSIP 216

Gabriel Roldan edited this page Mar 16, 2023 · 4 revisions

GSIP 216 - GeoFence 4.0.x

Overview

This proposal is to create a 4.0.x branch in GeoFence's github repository and advance in replacing the current 3.6.x series with the improvements from the Poof Of Concept branch we've been working on.

Back in April 2021, at Camptocamp we started thinking of integrating GeoFence into GeoServer Cloud, and initiated a discussion about some technology upgrades that would be needed or at least welcomed at GeoFence's side.

It's been up until now that we secured funding, and given the complexity of the endeavor, and that it required a developer to get very deep into GeoFence's codebase, we decided to start with a proof of concept development.

A month after, we have almost everything up and running, the confidence in the approach to overcome all the current limitations related to obsolete and deprecated dependencies; and hopefully to draw a brighter future for GeoFence where maintenance can be a shared responsibility between GeoSolutions and Camptocamp.

Proposed By

Gabriel Roldan (Camptocamp)

Assigned to Release

GeoFence: 4.0 GeoServer: TBD

State

  • Under Discussion
  • In Progress
  • Completed
  • Rejected
  • Deferred

TL;DR: try it out

Feel free to try the following branch from my GeoFence fork:

https://github.com/groldan/geofence/tree/4.0.x

Requirements:

  • Java 11
cd src/
mvn clean install

will create a single-jar executable at src/app/rest/target/geofence-rest-app-4.0-SNAPSHOT-exec.jar.

Run in development mode with an in-memory H2 database, either with

cd app/rest
mvn spring-boot:run -Dspring-boot.run.profiles=dev

or

java -jar target/geofence-rest-app-4.0-SNAPSHOT-exec.jar --spring.profiles.active=dev

Motivation

GeoFence has been around since 2014.

Some technology choices that made total sense by then, are now complicating its future. For instance:

  • The persistence abstractions are powered by Google's Generic DAO Framework, which is not maintained since years ago
  • The version of Hibernate Spatial used (1.1.3.2) is also aged and maintained at GeoSolutions's maven repository, as an adaptation from the latest official 1.1.1 version from 2016.
  • The REST APIs exposed by GeoFence's standalone server application, and the GeoFence embedded engine in GeoServer, have some differences, which makes it hard to use a REST client interchangeably.
  • When GeoServer talks to a remote GeoFence instance, it does it through Spring RMI (Remote Method Interface), which is deprecated as of Spring 5.3, and removed in Spring 6.0.

For these reasons, and because we need to run GeoFence in the context of GeoServer Cloud, which runs Spring Boot applications, Camptocamp is making this proposal to replace the deprecated dependencies with current alternatives, with a strong focus on homogenizing GeoFence's REST API so GeoServer/GeoFence interactions can rely on it as well as it currently does on Spring RMI.

Proposal

This proposal is to either create a 4.0.x branch in GeoFence's GitHub repository and advance in replacing the current 3.6.x series, with the following major goals:

  • Have a clearly defined REST API using OpenAPI 3.0 specifications
  • Use an API-first approach and automatic code generation to create server API stubs and ready-to-use client libraries
  • Implement Repository abstractions to hit the database directly (through JPA) as well as a remote service (though an OpenAPI Java Client), so that using the Rule, AdminRule, Authorization, User, and UserGroup services with either an embedded or remote GeoFence engine is straightforward and transparent.
  • Upgrade to a current version of Hibernate Spatial
  • Replace the D.A.O. interfaces and Google Generic DAO Framework with Repository abstractions that rely on GeoFence's RuleFilter predicate, leaving the details to the Repository implementations.
  • Provide default Repository adaptors over Spring Data JPA, which translate RuleFilter predicates to Querydsl predicates.

Project modules reorganization

The project modules are reorganized in order to have a clearer separation of concerns in terms of architecture and minimize dependencies.

The following diagram shows the major organizational units, which will be explored in more details in the following sections.

flowchart LR
  subgraph domain
    adminrule-management --> object-model & rule-management
    authorization --> adminrule-management & user-management & rule-management
    rule-management --> object-model
    user-management --> object-model
  end
  subgraph openapi-codegen
    openapi-server --> openapi-model
    openapi-client --> openapi-model
    openapi-js-client
    openapi-python-client
  end
  subgraph integration
    subgraph persistence-jpa
        jpa-integration --> jpa-persistence
    end
    subgraph spring-integration
        domain-spring-integration -. optional .-> rule-management & adminrule-management & user-management & authorization
    end
    subgraph openapi-integration
        api-model-mapper --> object-model & openapi-model
        api-impl --> api-model-mapper & openapi-server & domain-spring-integration & rule-management & adminrule-management & user-management
        api-client --> api-model-mapper & openapi-client
    end
    subgraph geoserver-integration
        access-manager --> 
          domain-spring-integration & authorization
        restconfig --> api-impl & access-manager
        webui --> access-manager
    end
  end
  subgraph application
    rest-app --> api-impl & jpa-integration
    plugin-embdedded --> jpa-integration & access-manager & restconfig
    plugin-remote --> api-client & access-manager
  end

domain

The domain architectural layer is comprised of a no-framework, pure-logic, set of components (i.e. no dependency on Spring nor any other framework), and the domain object model, which is immutable.

openapi-codegen

The openapi modules define the OpenAPI 3.0 interface as a set of yaml documents, which split the whole set into separate APIS for the following business domains:

  • Rule Management
  • Admin Rule Management
  • User Management
  • UserGroup Management

The openapi-codegen-maven-plugin takes care of creating Java, JavaScript, and Python client libraries, and the Java spring-webmvc server stub.

Note there's no OpenAPI interface for the Authorization domain since its components run on GeoServer. Instead, the OpenAPI interface is complete enough as to implement the Java-Client repository adaptors to run the Authorization domain components transparently against a remote GeoFence API service.

integration

The Integration architectural layer takes care of all the implementation details necessary to interface with persistence backends, remote services, and dependency-injection frameworks such as Spring-Context.

The domain-spring-integration comprises a number of @Configuration annotated Spring configuration classes to wire up all the necessary components to run GeoFence

application

The Application layer contains complete run-time deliverables. The rest-app module is a simple Spring-boot application that incorporates Spring-Boot AutoConfigurations to gather all together from:

  • domain-spring-integration module to wire up all business domain components with its collaborators.
  • integration-jpa to provide the Repository implementations that work against an RDBMS.
  • api-impl to expose the code-generated webmvc controllers for the REST API.
  • Additionally exposes the OpenAPI Swagger UI and the OpenAPI spec in JSON form through the springdoc-openapi-ui dependency.

Core domain business objects

The core domain business objects are split into separate modules:

---
title: Domain model dependency graph
---
flowchart LR
  subgraph external dependencies
    direction LR
    org.geolatte:geolatte-geom
    org.geotools:gt-main
  end
  subgraph org.geoserver.geofence.domain
    direction LR
    object-model --> org.geolatte:geolatte-geom
    rule-management --> object-model
    adminrule-management --> object-model
    user-management --> object-model
    authorization --> rule-management & adminrule-management & user-management
    authorization -. provided .-> org.geotools:gt-main
  end

The Object Model is decoupled from the persistence and OpenAPI interface object models. Mapping between these object models is performed by the Mapstruct library, which is compile-time only, and generates the mappers without additional dependencies, nor reflection used.

This decoupling also allows for certain design choices:

  • The type of identity keys in the core business model is changed from long to String, in order not to expose persistence layer implementation details (e.g. the JPA repository adaptors lightly obscure the database identity values by hex-encoding them).
  • The concept of "identifier" is introduced at the business object level and Rule is decoupled from LayerDetails:
---
title: Rule domain model
---
classDiagram
    class RuleIdentifier
    RuleIdentifier: -GrantType access
    RuleIdentifier: -String instance
    RuleIdentifier: -String username
    RuleIdentifier: -String rolename
    RuleIdentifier: -String service
    RuleIdentifier: -String request
    RuleIdentifier: -String subfield
    RuleIdentifier: -String workspace
    RuleIdentifier: -String layer
    RuleIdentifier: -IPAddressRange addressRange

    class Rule
    Rule: -String id
    Rule: -long priority
    Rule *-- RuleIdentifier : identifier
    Rule *-- RuleLimits : ruleLimits

    class RuleLimits

    LayerDetails "1" *-- "0..n" LayerAttributes

Which is slightly different from the persistence JPA object model

---
title: JPA Rule model
---
classDiagram
    class RuleIdentifier~@Embeddable~{
        -GrantType access
        -String instance
        ...
    }
    class RuleLimits~@Embeddable~{
    }
    class LayerDetails~@Embeddable~{
    }
    class LayerAttributes{
    }

    LayerDetails "1" *-- "0..n" LayerAttributes

    class Rule{
        -long id
        -long priority
    }
    Rule "1" *-- "1" RuleIdentifier : identifier
    Rule "1" *-- "0..1" RuleLimits : ruleLimits
    Rule "1" *-- "0..1" LayerDetails : layerDetails

Object Model Immutability

The domain object model is made immutable. Construction of objects such as Rule, AdminRule, etc., is implemented using the "Builder" pattern, and withXX instance methods that return copies with a single property difference. For example:

Rule allow = Rule.allow();
Rule allowWs1 = allow.withWorkspace("ws1");
Rule ws1WithUserAndService = allowWs1.toBuilder().user("gabe").service("WMS").build();

This provides the benefits of immutability and clarity as compared to the old full constructors:

Rule rule = new Rule(10, null, "p1", null, null, "s1", "r1", null, "w1", "l1", GrantType.ALLOW)

RuleLimits and LayerDetails have properties of type MultiPolygon. Whereas the JTS library MultiPolygon type was used, it's changed to the GeoLatte MultiPolygon type, as GeoLatte's geometry model is immutable. Both geometry libraries are natively supported by Hibernate-Spatial.

Converting from GeoLatte geometries to JTS geometries in the GeoServer plugin, for the sake of computing intersections, was profiled and has no impact on performance (due to GeoLatte coordinate sequences implementing JTS's CoordinateSequence and ensuring immutability by disallowing CoordinateSequence.setOrdinate(...)).

RuleFilter predicate

RuleFilter is split into RuleFilter and AdminRuleFilter, as they have different predicate properties. Both have been converted to run-time predicates (e.g. RuleFilter.matches(Rule):boolean), which allows to provision a reference implementation of the intended behavior for Repository implementations.

This, coupled with reference, test-scoped Repository implementations that work on memory only, allows running domain service integration tests without depending on particular repository implementations, which at the same time serve as base integration tests for both the JPA and OpenAPI-client integration tests.

Domain components

The core business model is a no-framework, pure-logic, set of components (i.e. no dependency on Spring nor any other framework).

Dependency injection is constructor-based and the details are left over to the integration architectural layer.

That is, Service components are concrete classes and expect collaborators through their constructors (e.g. new RuleAdminService(ruleRepository)).

---
title: Domain Components Diagram
---
flowchart TB
    subgraph Rule Management
        RuleAdminService[<< component >>\nRuleAdminService] --> RuleRepository[<< interface >>\nRuleRepository]
    end
    subgraph AdminRule Management
        AdminRuleAdminService[<< component >>\nAdminRuleAdminService] --> AdminRuleRepository[<< interface >>\nAdminRuleRepository]
    end
    subgraph User Management
        UserAdminService[<< component >>\nUserAdminService] --> UserRepository[<< interface >>\nUserRepository]
        UserGroupAdminService[<< component >>\nUserGroupAdminService] --> UserGroupRepository[<< interface >>\nUserGroupRepository]
    end
    subgraph Authorization
        UserRoleNamesResolver[<< component >>\nUserRoleNamesResolver] --> UserGroupAdminService
        RuleReaderService[<< component >>\nRuleReaderService] --> UserRoleNamesResolver & AdminRuleAdminService & RuleAdminService
        UserAuthorizationService[<< component >>\nUserAuthorizationService] --> UserAdminService
    end

Backwards Compatibility

There are backward compatibility concerns in terms of:

  • Breaking REST API.

    Since the REST API is completely new, we're using path-based API versioning. The standalone application sets it at /geofence/api/v2. For GeoServer, it might be possible to maintain the current (i.e. v1) API alongside the new one for, say, one or two major releases.

    The possibility of creating code-generated client libraries from the OpenAPI specification documents, in a myriad of supported programming languages and frameworks, can alleviate the situation and/or speed up the adoption of the new API.

  • Configuration mechanism.

    We'll try to maintain backward compatibility with the current configuration mechanism for the GeoServer plugins.

  • H2 database version.

    GeoServer does have the problem of an outdated H2 database version, currently using 1.1.119, released on Sep 26, 2009.

    An up-to-date version of Hibernate-Spatial requires a newer version. At the time of writing, 2.1.214 was released on Jun 14, 2022.

    The newer versions got an improved but incompatible on-disk file format.

    We would need to discuss a path forward, since automatic updates, though tricky, could be done in some cases, but at the same time may not be the best strategy as an upgrade policy.

Feedback

Voting

Project Steering Committee:

  • Alessio Fabiani:
  • Andrea Aime: -1 See email discussion
  • Ian Turton: +1
  • Jody Garnett: -0
  • Jukka Rahkonen:
  • Kevin Smith:
  • Simone Giannecchini:
  • Torben Barsballe:
  • Nuno Oliveira:

Links

Clone this wiki locally