[ 
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. 
WW-5462 is therefore realised as a supported {{struts2-mocks}} module built on 
{{struts2-api}} — scheduled alongside this ticket in the same release; see 
WW-5462.

h3. Scope note

Major change — targeted for *Struts 8.0.0* (together with WW-5462). Sequencing 
within 8.0.0: (C) step (i) internal package de-mixing first, then (ii) 
whole-package relocation into {{struts2-api}}, then the {{struts2-mocks}} 
module.



  was:
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.

 


> 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: 8.0.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. 
> WW-5462 is therefore realised as a supported {{struts2-mocks}} module built 
> on {{struts2-api}} — scheduled alongside this ticket in the same release; see 
> WW-5462.
> h3. Scope note
> Major change — targeted for *Struts 8.0.0* (together with WW-5462). 
> Sequencing within 8.0.0: (C) step (i) internal package de-mixing first, then 
> (ii) whole-package relocation into {{struts2-api}}, then the 
> {{struts2-mocks}} module.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to