From 8a3901379ac6c442d284141dc1f24e7d4a18ab68 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 15 Jul 2026 23:08:45 +0200 Subject: [PATCH 1/5] chore(spec-016): link issue #34 and mark spec in progress Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/specs/INDEX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index 1b2a901..c857a6d 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -17,5 +17,5 @@ | 013 | 013-dedicated-schema-and-starter | Dedicated schema + starter | backend, database, architecture, build | Move all 7 library tables into a fixed `oe_spring_services` schema (single persistence unit) and ship a real Spring Boot starter that registers the library package additively via AutoConfigurationPackages — zero-config consumption. Migrations stay consumer-managed; the library ships ready-to-apply per-release SQL in the upgrade doc. Includes the app→lib cross-entity reference contract | #25 | done | | 014 | 014-multi-module-restructuring | Multi-module restructuring | build, architecture, infrastructure | Restructure the single artifact into a Maven reactor: `spring-services-core` (all 7 entities, single persistence unit) + optional feature modules (`slack`, `mcp`, `email`, `search`, `dbbackup`) + `spring-services-all` aggregator jar + `spring-services-bom`. Lockstep versioning; per-module `@AutoConfiguration` guarded by `@ConditionalOnClass`; builds on spec 013 (released first). Breaking change: `spring-services` coordinate becomes the reactor parent, consumers migrate to `spring-services-all` | #27 | done | | 015 | 015-scim-users-provider | SCIM Users provider | backend, security, api, database | SCIM 2.0 service-provider first slice: an isolated `/scim/v2/**` bearer-token filter chain (static `openelements.scim.token`), discovery endpoints (ServiceProviderConfig/ResourceTypes/Schemas), and the Users resource (list/filter, POST, GET, PUT full-replace, soft DELETE) mapped onto spec 012's `UserEntity`. `POST` collision → `409 uniqueness`; DELETE is soft (`deleted`/`deleted_at`); writes audited under a reserved SCIM service principal. Groups+membership and Group→Role mapping deferred to follow-up issues; grounded in real Authentik 2026.5.4 traffic (#21) with a pre-merge verification test plan. | #21 | done | -| 016 | 016-tenant-feature-module | Tenant feature module | build, architecture | Extract the `com.openelements.spring.base.tenant` package out of `spring-services-core` into a new optional feature module `spring-services-tenant` (depends on core), consistent with the spec-014 module pattern. Self-activates via `TenantAutoConfiguration`; `TenantConfig` drops its `@ComponentScan` in favour of an explicit `TenantService` `@Bean` to avoid double registration. Core loses its only two tenant references (`FullSpringServiceConfig` `@Import`, a `data/package-info.java` Javadoc link). Added to reactor + `spring-services-all` + BOM. Build-coordinate change only (no API/schema/behaviour change); breaking for à-la-carte core consumers of tenant types → documented in the 1.4.0 upgrade guide. | — | open | +| 016 | 016-tenant-feature-module | Tenant feature module | build, architecture | Extract the `com.openelements.spring.base.tenant` package out of `spring-services-core` into a new optional feature module `spring-services-tenant` (depends on core), consistent with the spec-014 module pattern. Self-activates via `TenantAutoConfiguration`; `TenantConfig` drops its `@ComponentScan` in favour of an explicit `TenantService` `@Bean` to avoid double registration. Core loses its only two tenant references (`FullSpringServiceConfig` `@Import`, a `data/package-info.java` Javadoc link). Added to reactor + `spring-services-all` + BOM. Build-coordinate change only (no API/schema/behaviour change); breaking for à-la-carte core consumers of tenant types → documented in the 1.4.0 upgrade guide. | #34 | in progress | From 631ae799adfe1565c0c9634b9762736cbf5780e9 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 15 Jul 2026 23:28:21 +0200 Subject: [PATCH 2/5] feat(tenant)!: extract multi-tenancy into spring-services-tenant module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec 016. Moves the com.openelements.spring.base.tenant package out of spring-services-core into a new optional feature module spring-services-tenant (depends on core), registered in the reactor, spring-services-all and the BOM. - TenantAutoConfiguration (@Import(TenantConfig)) self-activates on classpath presence via AutoConfiguration.imports; no @ConditionalOnClass (no third-party dep). - TenantConfig drops @ComponentScan and registers TenantService as an explicit @Bean (TenantService loses @Service/@Autowired), so the co-located auto-config is not double-registered. - Core cleanup: FullSpringServiceConfig no longer @Imports TenantConfig; the data/package-info tenant @link becomes plain text so core Javadoc stays clean under failOnWarnings. Build-coordinate change only — no API/schema/behaviour change to the tenancy logic. Breaking for a-la-carte consumers who used tenant types via core (add the module). Spec: docs/specs/016-tenant-feature-module (#34) Co-Authored-By: Claude Opus 4.8 (1M context) --- pom.xml | 1 + spring-services-all/pom.xml | 5 +++ spring-services-bom/pom.xml | 5 +++ .../spring/base/FullSpringServiceConfig.java | 4 --- .../spring/base/data/package-info.java | 5 +-- .../spring/base/tenant/TenantConfig.java | 19 ----------- spring-services-tenant/pom.xml | 32 +++++++++++++++++++ ...bstractMultitenantDbBackedDataService.java | 0 .../tenant/AbstractMultitenantEntity.java | 0 .../spring/base/tenant/EnableTenant.java | 0 .../tenant/EntityWithMultitenantSupport.java | 0 .../tenant/RepositoryWithTenantSupport.java | 0 .../base/tenant/TenantAutoConfiguration.java | 26 +++++++++++++++ .../spring/base/tenant/TenantConfig.java | 31 ++++++++++++++++++ .../spring/base/tenant/TenantService.java | 7 ++-- .../spring/base/tenant/package-info.java | 0 ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../spring/base/tenant/TenantServiceTest.java | 0 18 files changed, 107 insertions(+), 29 deletions(-) delete mode 100644 spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java create mode 100644 spring-services-tenant/pom.xml rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantDbBackedDataService.java (100%) rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantEntity.java (100%) rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/EnableTenant.java (100%) rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/EntityWithMultitenantSupport.java (100%) rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/RepositoryWithTenantSupport.java (100%) create mode 100644 spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantAutoConfiguration.java create mode 100644 spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/TenantService.java (91%) rename {spring-services-core => spring-services-tenant}/src/main/java/com/openelements/spring/base/tenant/package-info.java (100%) create mode 100644 spring-services-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename {spring-services-core => spring-services-tenant}/src/test/java/com/openelements/spring/base/tenant/TenantServiceTest.java (100%) diff --git a/pom.xml b/pom.xml index 67691cd..802ec0c 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,7 @@ spring-services-email spring-services-mcp spring-services-scim + spring-services-tenant spring-services-all spring-services-bom diff --git a/spring-services-all/pom.xml b/spring-services-all/pom.xml index 97c8fd7..921399f 100644 --- a/spring-services-all/pom.xml +++ b/spring-services-all/pom.xml @@ -52,6 +52,11 @@ spring-services-scim ${project.version} + + com.open-elements + spring-services-tenant + ${project.version} + diff --git a/spring-services-bom/pom.xml b/spring-services-bom/pom.xml index f5c621a..5ee0b34 100644 --- a/spring-services-bom/pom.xml +++ b/spring-services-bom/pom.xml @@ -53,6 +53,11 @@ spring-services-scim ${project.version} + + com.open-elements + spring-services-tenant + ${project.version} + com.open-elements spring-services-all diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java b/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java index d84461b..ac5af32 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java +++ b/spring-services-core/src/main/java/com/openelements/spring/base/FullSpringServiceConfig.java @@ -8,7 +8,6 @@ import com.openelements.spring.base.services.tag.TagConfig; import com.openelements.spring.base.services.translation.TranslationConfig; import com.openelements.spring.base.services.webhook.WebhookConfig; -import com.openelements.spring.base.tenant.TenantConfig; import org.springframework.context.annotation.Import; /** @@ -29,8 +28,6 @@ * *
    *
  • {@link SecurityConfig} — JWT and API-key authentication. - *
  • {@link TenantConfig} — multi-tenancy support (entities, repositories, current-tenant - * resolution). *
  • {@link ApiKeyConfig} — API-key data layer (entity, repository, data service). *
  • {@link SettingsConfig} — generic key/value settings store. *
  • {@link TagConfig} — taggable-entity support. @@ -40,7 +37,6 @@ */ @Import({ SecurityConfig.class, - TenantConfig.class, ApiKeyConfig.class, SettingsConfig.class, TagConfig.class, diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/data/package-info.java b/spring-services-core/src/main/java/com/openelements/spring/base/data/package-info.java index fb48fca..41a47d2 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/data/package-info.java +++ b/spring-services-core/src/main/java/com/openelements/spring/base/data/package-info.java @@ -132,7 +132,8 @@ * *

    Tenant-aware variants

    * - *

    For multi-tenant services, see {@link com.openelements.spring.base.tenant} which provides the - * tenant-scoped counterparts of the abstractions defined here. + *

    For multi-tenant services, see the {@code spring-services-tenant} module (package + * {@code com.openelements.spring.base.tenant}), which provides the tenant-scoped counterparts of the + * abstractions defined here. */ package com.openelements.spring.base.data; diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java b/spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java deleted file mode 100644 index a72ff01..0000000 --- a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.openelements.spring.base.tenant; - -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -/** - * Spring configuration for the multi-tenancy feature. - * - *

    Component-scans this package so that {@link TenantService} and the tenant-aware data - * abstractions are registered. Can be imported either directly, via {@link EnableTenant}, or as - * part of {@link com.openelements.spring.base.FullSpringServiceConfig}. - */ -@Configuration(proxyBeanMethods = false) -@ComponentScan(basePackageClasses = TenantConfig.class) -public class TenantConfig { - - /** Creates the configuration; the tenant beans are registered via component scanning. */ - public TenantConfig() {} -} diff --git a/spring-services-tenant/pom.xml b/spring-services-tenant/pom.xml new file mode 100644 index 0000000..16543c5 --- /dev/null +++ b/spring-services-tenant/pom.xml @@ -0,0 +1,32 @@ + + 4.0.0 + + + com.open-elements + spring-services + 1.4.0-SNAPSHOT + + + spring-services-tenant + + Spring Services Tenant + Optional row-level multi-tenancy feature module for spring-services + https://github.com/OpenElementsLabs/spring-services + + + + com.open-elements + spring-services-core + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantDbBackedDataService.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantDbBackedDataService.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantDbBackedDataService.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantDbBackedDataService.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantEntity.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantEntity.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantEntity.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/AbstractMultitenantEntity.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/EnableTenant.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/EnableTenant.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/EnableTenant.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/EnableTenant.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/EntityWithMultitenantSupport.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/EntityWithMultitenantSupport.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/EntityWithMultitenantSupport.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/EntityWithMultitenantSupport.java diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/RepositoryWithTenantSupport.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/RepositoryWithTenantSupport.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/RepositoryWithTenantSupport.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/RepositoryWithTenantSupport.java diff --git a/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantAutoConfiguration.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantAutoConfiguration.java new file mode 100644 index 0000000..ab5be64 --- /dev/null +++ b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantAutoConfiguration.java @@ -0,0 +1,26 @@ +package com.openelements.spring.base.tenant; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Import; + +/** + * Auto-configuration for the optional multi-tenancy feature. + * + *

    Registered through this module's + * {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports}, so the + * feature self-activates whenever {@code spring-services-tenant} is on the classpath. The module + * ships no optional third-party library, so — unlike the slack/mcp/email auto-configurations — there + * is no {@code @ConditionalOnClass} guard: present on the classpath means active. + * + *

    Co-locating in the {@code tenant} package is safe because {@link TenantConfig} registers + * {@link TenantService} through an explicit {@code @Bean} instead of a {@code @ComponentScan} that + * would otherwise sweep up this class. {@code @Import(TenantConfig.class)} / {@link EnableTenant} + * remain valid for explicit wiring. + */ +@AutoConfiguration +@Import(TenantConfig.class) +public class TenantAutoConfiguration { + + /** Creates the tenant auto-configuration; wiring is delegated to {@link TenantConfig}. */ + public TenantAutoConfiguration() {} +} diff --git a/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java new file mode 100644 index 0000000..7e10c62 --- /dev/null +++ b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantConfig.java @@ -0,0 +1,31 @@ +package com.openelements.spring.base.tenant; + +import com.openelements.spring.base.security.AuthService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Spring configuration for the multi-tenancy feature. + * + *

    Registers {@link TenantService} as an explicit {@code @Bean} (rather than component-scanning + * the package) so that {@link TenantAutoConfiguration}, which lives in the same package, is not + * swept up and registered twice. Can be imported directly, via {@link EnableTenant}, or by the + * module's auto-configuration. + */ +@Configuration(proxyBeanMethods = false) +public class TenantConfig { + + /** Creates the configuration. */ + public TenantConfig() {} + + /** + * Registers the tenant service. + * + * @param authService the service used to resolve the authenticated principal + * @return the tenant service + */ + @Bean + public TenantService tenantService(final AuthService authService) { + return new TenantService(authService); + } +} diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantService.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantService.java similarity index 91% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantService.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantService.java index 609f88d..fcaaa0b 100644 --- a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/TenantService.java +++ b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/TenantService.java @@ -2,8 +2,6 @@ import com.openelements.spring.base.security.AuthService; import java.util.Objects; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; /** * Resolves the tenant id of the current request. @@ -11,8 +9,10 @@ *

    The tenant id is taken verbatim from the {@code name} of the authenticated principal as * exposed by {@link AuthService#getPrincipal()}. For JWT-authenticated requests this is the {@code * sub} claim; for API-key requests it is the API key entity's name. + * + *

    Registered as a bean by {@link TenantConfig#tenantService(AuthService)} rather than by + * component scanning. */ -@Service public class TenantService { private final AuthService authService; @@ -22,7 +22,6 @@ public class TenantService { * * @param authService the service used to resolve the authenticated principal */ - @Autowired public TenantService(final AuthService authService) { this.authService = Objects.requireNonNull(authService, "authService must not be null"); } diff --git a/spring-services-core/src/main/java/com/openelements/spring/base/tenant/package-info.java b/spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/package-info.java similarity index 100% rename from spring-services-core/src/main/java/com/openelements/spring/base/tenant/package-info.java rename to spring-services-tenant/src/main/java/com/openelements/spring/base/tenant/package-info.java diff --git a/spring-services-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-services-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..00860a3 --- /dev/null +++ b/spring-services-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.openelements.spring.base.tenant.TenantAutoConfiguration diff --git a/spring-services-core/src/test/java/com/openelements/spring/base/tenant/TenantServiceTest.java b/spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantServiceTest.java similarity index 100% rename from spring-services-core/src/test/java/com/openelements/spring/base/tenant/TenantServiceTest.java rename to spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantServiceTest.java From 53ef5615790115af4edd1dddf0dedbac04249934 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 15 Jul 2026 23:28:21 +0200 Subject: [PATCH 3/5] test(tenant): move TenantServiceTest and add self-activation test Spec 016. TenantServiceTest moves with the package (plain JUnit + Mockito, passes unchanged). Adds TenantAutoConfigurationTest asserting the module self-activates with exactly one TenantService bean (no double registration after dropping @ComponentScan). Spec: docs/specs/016-tenant-feature-module (#34) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tenant/TenantAutoConfigurationTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantAutoConfigurationTest.java diff --git a/spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantAutoConfigurationTest.java b/spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantAutoConfigurationTest.java new file mode 100644 index 0000000..f1eeb81 --- /dev/null +++ b/spring-services-tenant/src/test/java/com/openelements/spring/base/tenant/TenantAutoConfigurationTest.java @@ -0,0 +1,30 @@ +package com.openelements.spring.base.tenant; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.openelements.spring.base.security.AuthService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * Verifies that {@link TenantAutoConfiguration} self-activates and registers exactly one + * {@link TenantService} bean — i.e. the switch from {@code @ComponentScan} to an explicit + * {@code @Bean} in {@link TenantConfig} does not double-register the service. + */ +@DisplayName("TenantAutoConfiguration — self-activation") +class TenantAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withBean(AuthService.class, AuthService::new) + .withConfiguration(AutoConfigurations.of(TenantAutoConfiguration.class)); + + @Test + @DisplayName("Exactly one TenantService bean is registered on the classpath") + void singleTenantServiceBean() { + contextRunner.run( + context -> assertThat(context).hasNotFailed().hasSingleBean(TenantService.class)); + } +} From e266e80027a202152c3bd165498357f16f62c8e5 Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 15 Jul 2026 23:28:21 +0200 Subject: [PATCH 4/5] docs(spec-016): document tenant module move in the 1.4.0 upgrade guide + README Spec: docs/specs/016-tenant-feature-module (#34) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 4 +++- docs/releases/upgrade-to-1.4.md | 31 ++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c926bed..1222318 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ coordinate is the reactor parent (a `pom`, no classes) — depend on one of the **À la carte:** import the BOM once, then declare `spring-services-core` plus only the feature modules you need (`spring-services-slack`, `spring-services-mcp`, `spring-services-email`, -`spring-services-search`, `spring-services-dbbackup`, `spring-services-scim`) without versions: +`spring-services-search`, `spring-services-dbbackup`, `spring-services-scim`, +`spring-services-tenant`) without versions: ```xml @@ -331,6 +332,7 @@ spring-services/ — reactor parent (packaging=pom) ├── spring-services-search — Meilisearch full-text search (RestClient, no extra dep) ├── spring-services-dbbackup — db-backup sidecar client (RestClient, no extra dep) ├── spring-services-scim — SCIM 2.0 Users provider (opt-in via openelements.scim.token) +├── spring-services-tenant — row-level multi-tenancy (self-activates on the classpath) ├── spring-services-all — everything bundle (depends on all modules; no config of its own) └── spring-services-bom — bill of materials for lockstep versioning ``` diff --git a/docs/releases/upgrade-to-1.4.md b/docs/releases/upgrade-to-1.4.md index 89acf7e..f86622e 100644 --- a/docs/releases/upgrade-to-1.4.md +++ b/docs/releases/upgrade-to-1.4.md @@ -1,4 +1,10 @@ -# Upgrade prompt: enabling the SCIM 2.0 Users provider (spec 015) +# Upgrade prompt: spring-services 1.4.0 optional-module changes + +`spring-services` 1.4.0 adds the optional **SCIM 2.0 Users provider** (spec 015) and moves +**multi-tenancy into its own module** (spec 016). Both sections below are independent — apply only +the ones you use. + +## SCIM 2.0 Users provider (spec 015) `spring-services` 1.4.0 adds an **optional** SCIM 2.0 Users service-provider module, `spring-services-scim`. It lets an external identity provider (Authentik, or any RFC 7644 client) @@ -83,3 +89,26 @@ credential. Rotation = change this property and the IdP token together; there is group write returns `501 Not Implemented`. Do not enable group provisioning in your IdP yet. - User `PATCH` is not implemented (`501`); Authentik updates via `PUT` full-replace. - Do not point your **application's own** entities at the reserved SCIM principal UUID. + +## Multi-tenancy moved to `spring-services-tenant` (spec 016) + +The multi-tenancy abstractions (`TenantService`, `AbstractMultitenantEntity`, +`AbstractMultitenantDbBackedDataService`, `RepositoryWithTenantSupport`, `EnableTenant`, +`TenantConfig`, …) previously lived in `spring-services-core`. In 1.4.0 they move, unchanged, into a +new optional feature module `spring-services-tenant`. No API, schema, or runtime-behaviour change to +the tenancy logic — this is a build-coordinate change only. + +- **`spring-services-all` consumers: nothing to do.** The everything-bundle now depends on + `spring-services-tenant`, so the feature is present exactly as before. +- **À-la-carte consumers who used tenant types via `spring-services-core`:** add the module + (version managed by the BOM): + + ```xml + + com.open-elements + spring-services-tenant + + ``` + +The feature self-activates on classpath presence (`TenantAutoConfiguration`); `@EnableTenant` and +`@Import(TenantConfig.class)` continue to work for explicit opt-in. There is no database migration. From 0aa591937dd3e7d6cceeeffab6caea1231ed617b Mon Sep 17 00:00:00 2001 From: Hendrik Ebbers Date: Wed, 15 Jul 2026 23:28:33 +0200 Subject: [PATCH 5/5] chore(spec-016): mark spec status as done Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/specs/INDEX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index c857a6d..8559cfe 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -17,5 +17,5 @@ | 013 | 013-dedicated-schema-and-starter | Dedicated schema + starter | backend, database, architecture, build | Move all 7 library tables into a fixed `oe_spring_services` schema (single persistence unit) and ship a real Spring Boot starter that registers the library package additively via AutoConfigurationPackages — zero-config consumption. Migrations stay consumer-managed; the library ships ready-to-apply per-release SQL in the upgrade doc. Includes the app→lib cross-entity reference contract | #25 | done | | 014 | 014-multi-module-restructuring | Multi-module restructuring | build, architecture, infrastructure | Restructure the single artifact into a Maven reactor: `spring-services-core` (all 7 entities, single persistence unit) + optional feature modules (`slack`, `mcp`, `email`, `search`, `dbbackup`) + `spring-services-all` aggregator jar + `spring-services-bom`. Lockstep versioning; per-module `@AutoConfiguration` guarded by `@ConditionalOnClass`; builds on spec 013 (released first). Breaking change: `spring-services` coordinate becomes the reactor parent, consumers migrate to `spring-services-all` | #27 | done | | 015 | 015-scim-users-provider | SCIM Users provider | backend, security, api, database | SCIM 2.0 service-provider first slice: an isolated `/scim/v2/**` bearer-token filter chain (static `openelements.scim.token`), discovery endpoints (ServiceProviderConfig/ResourceTypes/Schemas), and the Users resource (list/filter, POST, GET, PUT full-replace, soft DELETE) mapped onto spec 012's `UserEntity`. `POST` collision → `409 uniqueness`; DELETE is soft (`deleted`/`deleted_at`); writes audited under a reserved SCIM service principal. Groups+membership and Group→Role mapping deferred to follow-up issues; grounded in real Authentik 2026.5.4 traffic (#21) with a pre-merge verification test plan. | #21 | done | -| 016 | 016-tenant-feature-module | Tenant feature module | build, architecture | Extract the `com.openelements.spring.base.tenant` package out of `spring-services-core` into a new optional feature module `spring-services-tenant` (depends on core), consistent with the spec-014 module pattern. Self-activates via `TenantAutoConfiguration`; `TenantConfig` drops its `@ComponentScan` in favour of an explicit `TenantService` `@Bean` to avoid double registration. Core loses its only two tenant references (`FullSpringServiceConfig` `@Import`, a `data/package-info.java` Javadoc link). Added to reactor + `spring-services-all` + BOM. Build-coordinate change only (no API/schema/behaviour change); breaking for à-la-carte core consumers of tenant types → documented in the 1.4.0 upgrade guide. | #34 | in progress | +| 016 | 016-tenant-feature-module | Tenant feature module | build, architecture | Extract the `com.openelements.spring.base.tenant` package out of `spring-services-core` into a new optional feature module `spring-services-tenant` (depends on core), consistent with the spec-014 module pattern. Self-activates via `TenantAutoConfiguration`; `TenantConfig` drops its `@ComponentScan` in favour of an explicit `TenantService` `@Bean` to avoid double registration. Core loses its only two tenant references (`FullSpringServiceConfig` `@Import`, a `data/package-info.java` Javadoc link). Added to reactor + `spring-services-all` + BOM. Build-coordinate change only (no API/schema/behaviour change); breaking for à-la-carte core consumers of tenant types → documented in the 1.4.0 upgrade guide. | #34 | done |