feat(model): define inbound/outbound port interfaces for the hexagonal domain boundary#15
Merged
Merged
Conversation
added 3 commits
July 10, 2026 09:48
…odel Add 5 inbound and 6 outbound port interfaces under model.port that define the hexagonal boundary between the pure domain layer and platform adapters. All port method signatures use only model types and Java built-ins - zero Bukkit/NMS/vendored imports. Inbound ports (adapters call into domain): - ArmorSetEquipPort: equip/unequip/join/quit lifecycle - ArmorCombatPort: deal/take damage, fall, hunger, target-killed - ArmorCommandPort: give set, open gui, reload - ArmorPurchasePort: purchase a set piece - ArmorSetQueryPort: is-wearing, current set, pieces-worn, stats Outbound ports (domain asks the platform): - PlayerStatePort: worn armor, held item, speeds, health, damage, teleport, velocity, food - NbtPort: get/set NBT tags on items - EffectsPort: potion, lightning, TNT, falling blocks - MessagingPort: message, sound, command dispatch - EconomyPort: balance, withdraw, availability check - SchedulerPort: run-repeating, cancel Supporting pure value types for port signatures: - WornArmor, WorldPosition, Velocity (model.player) - ItemHandle, TaskHandle (model.id) - GiveResult (model.set) All new value types with executable logic are added to the JaCoCo core coverage gate with full test coverage.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Intent
Define the port interfaces that connect the pure model layer to the outside world - the hexagonal boundary. 5 inbound ports (ArmorSetEquipPort, ArmorCombatPort, ArmorCommandPort, ArmorPurchasePort, ArmorSetQueryPort) and 6 outbound ports (PlayerStatePort, NbtPort, EffectsPort, MessagingPort, EconomyPort, SchedulerPort) under model.port.inbound and model.port.outbound sub-packages. All port method signatures use ONLY model types and Java built-ins - zero Bukkit/NMS/vendored types. Supporting pure value types (WornArmor, WorldPosition, Velocity, ItemHandle, TaskHandle, GiveResult) were added to express port signatures without leaking platform types. This is purely additive - no existing production classes are modified (only AGENTS.md docs and pom.xml JaCoCo gate). The new value types with executable logic are added to the core coverage gate. Design decisions: ports are Java interfaces with Javadoc, adapters implement them outside the model; ItemHandle and TaskHandle are opaque typed-string handles rather than leaking Bukkit ItemStack or int taskId; WorldPosition and Velocity replace Bukkit Location and Vector; WornArmor is a map-based snapshot of equipped slots to set IDs; GiveResult is an enum for command outcomes. No Manager suffix anywhere per project convention.
What Changed
ArmorSetEquipPort,ArmorCombatPort,ArmorCommandPort,ArmorPurchasePort,ArmorSetQueryPort) and 6 outbound ports (PlayerStatePort,NbtPort,EffectsPort,MessagingPort,EconomyPort,SchedulerPort) undergg.steve.mc.ap.model.port, all expressed purely in model types and Java built-ins with zero Bukkit/NMS/vendored imports.WornArmor,WorldPosition,Velocity,ItemHandle,TaskHandle, andGiveResult, with unit tests for each; the new types with executable logic were added to the JaCoCo 100% core coverage gate inpom.xml.SchedulerPort.runRepeatingnow takes separate initial-delay and period parameters (matching Bukkit'srunTaskTimersemantics),ItemHandle/TaskHandleextendStringIdinstead of duplicating validation, andWorldPositionrejects a null world.AGENTS.md/README.mddocs were synced to the new port and coverage-gate lists.Risk Assessment
✅ Low: Purely additive change introducing interfaces and value types with no modifications to existing production logic; all previous findings were fixed, tests cover new value types, and the model layer remains Bukkit-free.
Testing
The hexagonal port layer works as intended: all 11 port interfaces and 6 supporting value types compile to Java 8 bytecode, ship in the plugin jar, are exercisable without Bukkit at runtime, and the 100% coverage gate on executable value types passes. An end-to-end standalone demo proves adapters can implement ports and drive the domain model through them without any platform dependency - the core architectural promise of this change.
Evidence: Port purity check (zero platform imports)
Evidence: Port API surface (javap of all 11 interfaces)
Evidence: Port boundary demo transcript (no-Bukkit end-to-end)
== Hexagonal port boundary demo (no Bukkit on classpath) == java.version=25.0.3 -- player joins wearing a saved helmet -- [domain] 11111111-2222-3333-4444-555555555555 joined wearing 1 piece(s) helmet slot resolves to: dragon -- player equips chestplate -- [domain] 11111111-2222-3333-4444-555555555555 equipped 'dragon' on CHESTPLATE now wearing 2 piece(s); isEmpty=false -- domain schedules an ability tick via outbound SchedulerPort -- [domain] ability tick fired for 11111111-2222-3333-4444-555555555555 received opaque handle: fake-task-42 [platform] cancelled task fake-task-42 -- pure value types used in port signatures -- WorldPosition: WorldPosition(world=world, x=100.5, y=64.0, z=-20.25, yaw=90.0, pitch=0.0) Velocity: Velocity(x=0.0, y=1.5, z=0.0) ItemHandle: item-abc123 (equals same value: true) GiveResult values: [SUCCESS, SET_NOT_FOUND, PLAYER_NOT_FOUND] -- WornArmor defensive copy check -- slots map is unmodifiable (UnsupportedOperationException) - defensive copy OK -- typed-string handle null/type safety -- ItemHandle.of(null) rejected: id must not be null ItemHandle vs TaskHandle same string equal? false (different ID types never equal) == DEMO COMPLETE: domain driven end-to-end through ports with zero platform types ==Evidence: New value-type test results (18 tests, all green)
Evidence: Coverage gate passes with new includes
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed ✅
src/main/java/gg/steve/mc/ap/model/port/outbound/SchedulerPort.java:14- SchedulerPort.runRepeating takes a single 'delayTicks' parameter, but Bukkit's runTaskTimer requires two longs: initial delay and repeat period. All existing usages (FairySetData, ColorWaySetData, LightningAttackUtil, EngineerAttackUtil) pass both (e.g. '0L, this.delay'). The port cannot express this distinction, and adapters won't know whether delayTicks is the delay, the period, or both.src/main/java/gg/steve/mc/ap/model/id/ItemHandle.java:9- ItemHandle and TaskHandle extend TypedString directly with manual null/empty validation, diverging from the established pattern where all other string-based leaf IDs (ArmorSetId, DamageCauseId, PotionEffectId, SoundId) extend StringId which already provides that validation. Extending StringId would eliminate the duplicate checks and align with the documented convention in AGENTS.md.src/main/java/gg/steve/mc/ap/model/player/WorldPosition.java:10- WorldPosition uses Lombok @value without null-checking the 'world' field. Other model types (TypedString, PlayerId) explicitly reject null in constructors. A null world would create a silently broken value object.🔧 Fix: Fix SchedulerPort signature, ID hierarchy, and null validation
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
JAVA_HOME=<jdk25> mvn -B clean verify (full build, 203 tests, JaCoCo check-core gate)mvn -B test -Dtest=ItemHandleTest,TaskHandleTest,VelocityTest,WorldPositionTest,WornArmorTest,GiveResultTest (18 new value-type tests)grep -rnE platform-import scan of entire src/main/java/gg/steve/mc/ap/model/ (zero violations)Standalone PortBoundaryDemo.java compiled+run against target/classes with only commons-lang3 - no Bukkit jar - exercises ArmorSetEquipPort, SchedulerPort, WornArmor builder/getSlot, defensive copy immutability, null rejection, cross-type inequality, WorldPosition, Velocity, ItemHandle, GiveResultjavap bytecode version check: port interfaces emit major version 52 (Java 8)unzip -l target/ArmorPlus-v2.3.6.jar confirms all 11 port interfaces + 5 value types present in the shaded artifact✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.