[ 
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)

Reply via email to