[
https://issues.apache.org/jira/browse/WW-4759?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Lukasz Lenart updated WW-4759:
------------------------------
Description:
h2. Findings: feasibility analysis of a dedicated \{{struts2-api}} artefact
_Investigation of the current 7.2.x \{{core}} module (769 main classes) to
scope what an API extraction actually requires. Summary: the work is dominated
by one structural constraint — split packages — and is binary-compatible if
done package-wise._
h3. 1. Split-package is the governing constraint
The \{{org.apache.struts2}} package mixes extension-point interfaces with their
implementations in the *same* package:
||Package \{{org.apache.struts2}}||API candidates (interfaces)||Implementations
(stay in core)||
|examples|\{{ActionInvocation}}, \{{ActionProxy}}, \{{ActionProxyFactory}},
\{{UnknownHandler}}|\{{DefaultActionInvocation}}, \{{DefaultActionProxy}},
\{{ActionContext}}, \{{ActionSupport}}|
Struts is published as OSGi bundles (felix \{{maven-bundle-plugin}}, default
\{{Export-Package}}), and JPMS is the strategic direction. *Both forbid a
package being exported from two jars.* Therefore the API cannot be split out
class-by-class — the cut must be at *whole-package granularity*: a package
moves entirely to \{{struts2-api}} or stays entirely in \{{struts2-core}}.
h3. 2. The interfaces themselves are dependency-clean
{\{ActionInvocation}}, \{{ActionProxy}} and \{{ObjectFactory}} import zero
\{{Default*}}/\{{Impl}} types — the interfaces do not reach back into
implementation. An interface layer would be a clean dependency-graph leaf. *The
problem is packaging, not coupling.*
h3. 3. "Basic classes in the API" widens the boundary
If \{{struts2-api}} is to carry foundational classes users can build on (not
just interfaces), the boundary grows. \{{ActionContext}} — one of the most
basic classes — transitively pulls:
{code}
org.apache.struts2.dispatcher.HttpParameters
org.apache.struts2.dispatcher.mapper.ActionMapping
org.apache.struts2.util.ValueStack
org.apache.struts2.dispatcher.DispatcherConstants
{code}
Each candidate is individually low-coupling (0–3 struts imports), but the API
surface spans roughly nine packages: the top-level extension interfaces plus
\{{inject}}, \{{conversion}}, \{{text}}, \{{locale}}, parts of \{{util}},
\{{config}}/\{{config.entities}}, and the \{{interceptor}} base
(\{{AbstractInterceptor}}, \{{Interceptor}}). Wide, not deep.
h3. 4. Binary compatibility is preserved under two rules
# FQCNs never change (a class keeps its exact \{{org.apache.struts2.*}} name).
# \{{struts2-core}} keeps a *compile* dependency on \{{struts2-api}}.
Under both, existing user imports resolve transitively — *no user migration
required*. This is what makes a whole-package move viable rather than a
breaking change.
h3. 5. Remediation options (the real design decision)
||Option||OSGi/JPMS clean||User source-compat||Notes||
|(A) Move whole packages, FQCN preserved|Yes|Yes|But impls sharing a package
with interfaces (\{{Default*}}, \{{ActionContext}} in \{{org.apache.struts2}})
get dragged into \{{struts2-api}} too — defeats "api = no impl".|
|(B) New \{{org.apache.struts2.api.*}} namespace|Yes|*No* — breaks every user
import|Non-starter for a compatibility-sensitive framework.|
|(C) De-mix first, then move *(recommended)*|Yes|Yes (internal-only
churn)|Relocate internal impls out of shared packages (e.g.
\{{DefaultActionInvocation}} → an \{{…impl}}/\{{…factory}} subpackage), leaving
pure interface/base packages that then move to \{{struts2-api}} wholesale with
public FQCNs intact. \{{Default*}} classes are rarely referenced directly by
users, so the blast radius is small and internal.|
*Recommended path: (C).* Treat WW-4759 as (i) a series of low-risk internal
package de-mixing refactors, then (ii) a mechanical whole-package relocation
into the new module. Step (i) is where the review effort concentrates; step
(ii) is mostly moves.
h3. 6. Corollary — this unblocks the ideal outcome for WW-5462
Once \{{ActionContext}}, \{{AbstractInterceptor}}, \{{ActionConfig}},
\{{Configuration}} and the extension interfaces live in \{{struts2-api}}, a
future \{{struts2-mocks}} module can depend *only* on \{{struts2-api}}, and
\{{struts2-core}}'s own tests can then depend on \{{struts2-mocks}} (test
scope) with *no reactor cycle*:
{code}
struts2-api (interfaces + base classes, no struts deps)
struts2-mocks --compile--> struts2-api
struts2-core --compile--> struts2-api
struts2-core --test-----> struts2-mocks (no cycle: mocks never depends
on core)
{code}
The mock classes pin the *minimum* package set \{{struts2-api}} must contain.
Until then, WW-5462 removes those mocks from the published \{{struts2-core}}
jar via a filtered \{{test-jar}} (interim); see WW-5462. A supported
\{{struts2-mocks}} module is the post-WW-4759 follow-up.
h3. Scope note
This is a major change and is _not_ targeted for 7.3.0. The analysis is
intended to inform the eventual design; the FixVersion should be revisited when
(C) step (i) is scheduled.
was:Create a new Maven artefact {{struts2-api}} and move all the interfaces
there, those interfaces are extension points that can be used by users to
implement custom behaviour
> Dedicated struts2-api Maven artefact
> ------------------------------------
>
> Key: WW-4759
> URL: https://issues.apache.org/jira/browse/WW-4759
> Project: Struts 2
> Issue Type: New Feature
> Components: "New" API
> Affects Versions: 2.5.10
> Reporter: Lukasz Lenart
> Priority: Major
> Fix For: 7.3.0
>
>
> h2. Findings: feasibility analysis of a dedicated \{{struts2-api}} artefact
> _Investigation of the current 7.2.x \{{core}} module (769 main classes) to
> scope what an API extraction actually requires. Summary: the work is
> dominated by one structural constraint — split packages — and is
> binary-compatible if done package-wise._
> h3. 1. Split-package is the governing constraint
> The \{{org.apache.struts2}} package mixes extension-point interfaces with
> their implementations in the *same* package:
> ||Package \{{org.apache.struts2}}||API candidates
> (interfaces)||Implementations (stay in core)||
> |examples|\{{ActionInvocation}}, \{{ActionProxy}}, \{{ActionProxyFactory}},
> \{{UnknownHandler}}|\{{DefaultActionInvocation}}, \{{DefaultActionProxy}},
> \{{ActionContext}}, \{{ActionSupport}}|
> Struts is published as OSGi bundles (felix \{{maven-bundle-plugin}}, default
> \{{Export-Package}}), and JPMS is the strategic direction. *Both forbid a
> package being exported from two jars.* Therefore the API cannot be split out
> class-by-class — the cut must be at *whole-package granularity*: a package
> moves entirely to \{{struts2-api}} or stays entirely in \{{struts2-core}}.
> h3. 2. The interfaces themselves are dependency-clean
> {\{ActionInvocation}}, \{{ActionProxy}} and \{{ObjectFactory}} import zero
> \{{Default*}}/\{{Impl}} types — the interfaces do not reach back into
> implementation. An interface layer would be a clean dependency-graph leaf.
> *The problem is packaging, not coupling.*
> h3. 3. "Basic classes in the API" widens the boundary
> If \{{struts2-api}} is to carry foundational classes users can build on (not
> just interfaces), the boundary grows. \{{ActionContext}} — one of the most
> basic classes — transitively pulls:
> {code}
> org.apache.struts2.dispatcher.HttpParameters
> org.apache.struts2.dispatcher.mapper.ActionMapping
> org.apache.struts2.util.ValueStack
> org.apache.struts2.dispatcher.DispatcherConstants
> {code}
> Each candidate is individually low-coupling (0–3 struts imports), but the API
> surface spans roughly nine packages: the top-level extension interfaces plus
> \{{inject}}, \{{conversion}}, \{{text}}, \{{locale}}, parts of \{{util}},
> \{{config}}/\{{config.entities}}, and the \{{interceptor}} base
> (\{{AbstractInterceptor}}, \{{Interceptor}}). Wide, not deep.
> h3. 4. Binary compatibility is preserved under two rules
> # FQCNs never change (a class keeps its exact \{{org.apache.struts2.*}} name).
> # \{{struts2-core}} keeps a *compile* dependency on \{{struts2-api}}.
> Under both, existing user imports resolve transitively — *no user migration
> required*. This is what makes a whole-package move viable rather than a
> breaking change.
> h3. 5. Remediation options (the real design decision)
> ||Option||OSGi/JPMS clean||User source-compat||Notes||
> |(A) Move whole packages, FQCN preserved|Yes|Yes|But impls sharing a package
> with interfaces (\{{Default*}}, \{{ActionContext}} in
> \{{org.apache.struts2}}) get dragged into \{{struts2-api}} too — defeats "api
> = no impl".|
> |(B) New \{{org.apache.struts2.api.*}} namespace|Yes|*No* — breaks every user
> import|Non-starter for a compatibility-sensitive framework.|
> |(C) De-mix first, then move *(recommended)*|Yes|Yes (internal-only
> churn)|Relocate internal impls out of shared packages (e.g.
> \{{DefaultActionInvocation}} → an \{{…impl}}/\{{…factory}} subpackage),
> leaving pure interface/base packages that then move to \{{struts2-api}}
> wholesale with public FQCNs intact. \{{Default*}} classes are rarely
> referenced directly by users, so the blast radius is small and internal.|
> *Recommended path: (C).* Treat WW-4759 as (i) a series of low-risk internal
> package de-mixing refactors, then (ii) a mechanical whole-package relocation
> into the new module. Step (i) is where the review effort concentrates; step
> (ii) is mostly moves.
> h3. 6. Corollary — this unblocks the ideal outcome for WW-5462
> Once \{{ActionContext}}, \{{AbstractInterceptor}}, \{{ActionConfig}},
> \{{Configuration}} and the extension interfaces live in \{{struts2-api}}, a
> future \{{struts2-mocks}} module can depend *only* on \{{struts2-api}}, and
> \{{struts2-core}}'s own tests can then depend on \{{struts2-mocks}} (test
> scope) with *no reactor cycle*:
> {code}
> struts2-api (interfaces + base classes, no struts deps)
> struts2-mocks --compile--> struts2-api
> struts2-core --compile--> struts2-api
> struts2-core --test-----> struts2-mocks (no cycle: mocks never
> depends on core)
> {code}
> The mock classes pin the *minimum* package set \{{struts2-api}} must contain.
> Until then, WW-5462 removes those mocks from the published \{{struts2-core}}
> jar via a filtered \{{test-jar}} (interim); see WW-5462. A supported
> \{{struts2-mocks}} module is the post-WW-4759 follow-up.
> h3. Scope note
> This is a major change and is _not_ targeted for 7.3.0. The analysis is
> intended to inform the eventual design; the FixVersion should be revisited
> when (C) step (i) is scheduled.
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)