GSIP 216
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.
Gabriel Roldan (Camptocamp)
GeoFence: 4.0 GeoServer: TBD
- Under Discussion
- In Progress
- Completed
- Rejected
- Deferred
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
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 official1.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 Spring6.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.
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'sRuleFilter
predicate, leaving the details to theRepository
implementations. - Provide default
Repository
adaptors over Spring Data JPA, which translateRuleFilter
predicates to Querydsl predicates.
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
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.
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.
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
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 theRepository
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.
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
toString
, 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 fromLayerDetails
:
---
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
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
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.
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
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.
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:
- Implement geofence standalone as a spring-boot application discussion at GeoFence GitHub issues
- Where do we go with the standalone GUI? discussion at GeoFence GitHub issues
- GeoFence 4.0.x Proof of Concept branch
- GeoTools / GeoServer Meeting 2023-02-28
©2022 Open Source Geospatial Foundation