Skip to content

refactor(api): implementation of sealed unions - #232

Merged
theShadowsDust merged 90 commits into
mainfrom
refactor/api-sealed-unions
Aug 5, 2026
Merged

refactor(api): implementation of sealed unions#232
theShadowsDust merged 90 commits into
mainfrom
refactor/api-sealed-unions

Conversation

@theShadowsDust

@theShadowsDust theShadowsDust commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Overview

This is a major architectural refactor that transforms PandorasCluster's API and data layer from a DTO-based mapper framework to a sealed-union result type pattern, combined with entity-to-record conversion. The refactor eliminates the abstraction gap between DTOs and entities while introducing type-safe, discriminated error handling throughout the service layer.

Submitter Checklist

  • Make sure you are opening from a topic branch (/feature/fix/docs/ branch (right side)) and not your main branch.
  • Ensure that the pull request title represents the desired changelog entry.
  • New public fields and methods are annotated with @since TODO.
  • I read and followed the contribution guidelines.

TheMeinerLP and others added 30 commits April 18, 2026 19:53
Each of the 9 flag operations wrapped the SessionFactory in
try-with-resources, closing the shared singleton after the first call.
Subsequent flag operations then failed with "SessionFactory is closed".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The entity-to-model direction was constructing a new HomePositionEntity
instead of the HomePosition API model, so callers received an entity
where they expected the DTO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously a HibernateException was swallowed, databaseService was left
null, and the plugin continued to enable with null service references,
NPEing on the first command. Throw IllegalStateException instead so the
plugin entry point can log and disable cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the PandorasClusterImpl construction in onEnable so a failed
Hibernate bootstrap no longer leaves the plugin half-initialized — the
PluginManager disables the plugin and the error is surfaced in the log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- removeClaimedChunk hard-coded return false, so callers never saw a
  successful deletion.
- getClaimedChunk declared a :chunkIndex named parameter but never bound
  it, making the query return an arbitrary row.
- Document unclaimArea as a best-effort composition of already-
  transactional sub-operations so future readers do not assume atomicity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- createLand now persists the land, its home, its flag container, the
  default LandArea and its initial ClaimedChunk in a single transaction
  so a partial failure rolls back cleanly. Previously the area creation
  ran after the outer commit, leaving orphan rows on exception.
- unclaimLand moves its cross-service sub-calls (removeFlagsFromLand,
  landAreaService::unclaimArea) outside the session, then loads the
  LandEntity by id and removes it along with its HomePositionEntity.
  The old code called session.remove(land) with the API model.
- getLands eagerly fetches owner, home, flagContainer and areas to
  prevent LazyInitializationException after the session closes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without @joincolumn, Hibernate generated implementation-defined FK
column names. Bind owner/home/flagContainerEntity to owner_id, home_id
and flag_container_id respectively, all NOT NULL, and mark the
flag_container side as the owning side of the OneToOne (the inverse
mappedBy moves to FlagContainerEntity). Add id-based equals/hashCode
with a null-id identity fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the unmapped OneToOne so the LandEntity side alone owns the FK
column (flag_container_id). Add id-based equals/hashCode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A player can be a member of multiple land areas, so the OneToOne was
semantically wrong — the second addLandMember for the same player would
violate the uniqueness implied by OneToOne. Also add an explicit
member_id join column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace dialect-specific columnDefinition with portable length /
nullable / unique annotations. uuid becomes the natural unique key
(length 36), name is length 16 NOT NULL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A position without world + coordinates has no meaning; declare every
column non-nullable and give world a 64-char bound.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
name had no @column annotation, so Hibernate generated an unbounded
nullable column. Declare length 64 NOT NULL and add id-based
equals/hashCode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename chunkIndex to chunk_index and add a composite unique constraint
on (landArea_id, chunk_index) so the same chunk can no longer be
claimed twice in the same area. Also add id-based equals/hashCode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add length/NOT NULL on name, state, role and flagContainer_id, plus a
uk_role_flags_container_name unique constraint so the same role flag
cannot be persisted twice for one container. Add id-based equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add length/NOT NULL on name, state and flagContainer_id, plus a
uk_natural_flags_container_name unique constraint. Add id-based
equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add length/NOT NULL on name, spawn_limit and flagContainer_id, plus a
uk_entitycap_flags_container_name unique constraint. Add id-based
equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getChunks was feeding ClaimedChunkDto instances into a new
LandAreaMappingStrategy, so the instanceof check failed and every
chunk came back null. Use ClaimedChunkMappingStrategy.create()
instead. Also document that the strategy must run inside an open
Hibernate session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three flag collections were previously mapped through a single
LandMappingStrategy instance, so every NaturalFlag / RoleFlag /
EntityCapFlag failed the instanceof check and came back null. Wire each
collection to its own NaturalFlagMappingStrategy / RoleFlagMappingStrategy
/ EntityCapFlagMappingStrategy and keep the land link on its own context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The common module builds the SessionFactory, so the <mapping class="…"/>
list lives here. The plugin-side hibernate.cfg.xml becomes redundant and
is removed in a follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entity mappings now live in common/hibernate.cfg.xml. Keeping two copies
invited drift — the plugin copy had the mappings while the common copy
had the dialect/connection block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test config pointed at localhost PostgreSQL, so a developer needed a
running Postgres container to execute :common:test. Switch to an
in-memory H2 database with H2Dialect and create-drop so tests can run in
isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New in-memory test configuration in connection.cfg.xml needs the H2
driver at runtime for :common:test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The common module does not apply the Kotlin plugin, so this test
support file never compiled. It also referenced DBO classes that were
removed long ago.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead test support code referencing removed DatabaseService contract.
Common module has no Kotlin plugin applied so it never compiled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead test support code referencing removed DBO types. Common module has
no Kotlin plugin applied so it never compiled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead test support code referencing removed DBO types. Common module has
no Kotlin plugin applied so it never compiled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead test support code referencing removed DBO types. Common module has
no Kotlin plugin applied so it never compiled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead test support code referencing a removed PandorasClusterApi
interface and the wrong service constructors. Common module has no
Kotlin plugin applied so it never compiled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The liquibase Gradle plugin was applied but never configured — no
changelog file, no liquibase {} block, no activities. Schema is owned
by Hibernate via hbm2ddl.auto=update, so remove the dead alias.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ldEntity to LandEntity for improved data structure
@theShadowsDust theShadowsDust changed the title Refactor/api sealed unions refactor(api): implementation of sealed unions Aug 3, 2026
@theShadowsDust
theShadowsDust marked this pull request as ready for review August 3, 2026 21:01
@theShadowsDust
theShadowsDust requested a review from a team as a code owner August 3, 2026 21:01

@TheMeinerLP TheMeinerLP left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@theShadowsDust
theShadowsDust merged commit 66f05b9 into main Aug 5, 2026
4 checks passed
@theShadowsDust
theShadowsDust deleted the refactor/api-sealed-unions branch August 5, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants