Skip to content

Implement V3 Metadata CRUD API#171

Merged
em3s merged 43 commits intomainfrom
feature/v3-metadata-api
Feb 6, 2026
Merged

Implement V3 Metadata CRUD API#171
em3s merged 43 commits intomainfrom
feature/v3-metadata-api

Conversation

@em3s
Copy link
Copy Markdown
Contributor

@em3s em3s commented Feb 4, 2026

Summary

Implement V3 Metadata CRUD API that wraps V2 DDL services with a V3 interface.

Architecture: Controller → V3CompatService → V2 DDL Service → Graph

Implementation Plan

Phase 1: Core Changes

  • Modify DatabaseUpdateRequest - add active: Boolean?, make comment optional
  • Add TableDescriptor.MultiEdge for multiEdge support

Phase 2: V3MetadataConverter

  • Create V3MetadataConverter.kt with conversion functions:
    • DatabaseDescriptor ↔ ServiceEntity
    • TableDescriptor.Edge ↔ LabelEntity (INDEXED)
    • TableDescriptor.MultiEdge ↔ LabelEntity (MULTI_EDGE)
    • AliasDescriptor ↔ AliasEntity
    • Storage: V3 enforces datastore:// URI format

Phase 3: V3CompatService

  • Create V3CompatService.kt wrapping V2 DDL services
    • Database CRUD (via serviceDdl)
    • Table CRUD (via labelDdl) - returns polymorphic TableDescriptor<*>
    • Alias CRUD (via aliasDdl)

Phase 4: Request DTOs

  • TableCreateRequest.kt - includes table field
  • TableUpdateRequest.kt
  • AliasCreateRequest.kt - includes alias field
  • AliasUpdateRequest.kt

Phase 5: Controllers

  • DatabaseController.kt - /graph/v3/databases/**
  • TableController.kt - /graph/v3/databases/{db}/tables/**
  • AliasController.kt - /graph/v3/databases/{db}/aliases/**

Phase 6: Tests

  • V3MetadataConverterTest.kt - Unit tests
  • V2V3CompatibilityTest.kt - V2-V3 compatibility E2E tests
  • DatabaseControllerTest.kt - E2E tests
  • TableControllerTest.kt - E2E tests
  • AliasControllerTest.kt - E2E tests

Phase 7: Bug Fixes

  • Make GitProperties optional for git worktree environments

Phase 8: Documentation

  • Update metadata.mdx - V3 API reference
  • Remove deprecated Korean V2 API documentation

V2-V3 Compatibility

Terminology Mapping

V2 Term V3 Term
Service Database
Label Table
Alias Alias

Table Type Mapping

V2 LabelType V3 Table Type Direction Note
INDEXED edge V2 ↔ V3 Bidirectional
HASH edge V2 → V3 V2 read only (HASH is deprecated)
MULTI_EDGE multiEdge V2 ↔ V3 Bidirectional

MutationMode Mapping

V2 MutationMode V3 MutationMode
SYNC SYNC
ASYNC ASYNC
IGNORE DROP
- DENY (V3 only)

REST API Design

POST Endpoints (Resource Name in Body)

Following REST design principles, POST endpoints do not include the resource name in URL:

Operation Endpoint Name Location
Create Database POST /graph/v3/databases database in body
Create Table POST /graph/v3/databases/{db}/tables table in body
Create Alias POST /graph/v3/databases/{db}/aliases alias in body

V3 Alias Constraints

V3 aliases can only reference tables within the same database. Cross-database aliases (supported in V2 via database.table format) are not available in V3.

How to Test

# All v3 metadata tests
./gradlew :server:test --tests "com.kakao.actionbase.server.api.graph.v3.metadata.*" -x generateGitProperties

Generated with Claude Code

Prepare for V3 Metadata API implementation:
- Add optional `active` field to DatabaseUpdateRequest
- Change `comment` to optional in DatabaseUpdateRequest
- Update DatabaseDescriptor.toUpdateRequest() to include active

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@em3s em3s marked this pull request as draft February 4, 2026 15:30
@dosubot dosubot bot added size:XS This PR changes 0-9 lines, ignoring generated files. enhancement New feature or request labels Feb 4, 2026
em3s and others added 26 commits February 5, 2026 00:31
Add conversion functions for:
- DatabaseDescriptor <-> ServiceEntity
- TableDescriptor.Edge <-> LabelEntity
- AliasDescriptor <-> AliasEntity
- MutationMode, DirectionType, Storage, Schema conversions

V3 enforces datastore:// URI format for storage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add V3CompatService wrapping V2 DDL services:
- Database CRUD via serviceDdl
- Table CRUD via labelDdl
- Alias CRUD via aliasDdl

Add request DTOs:
- TableCreateRequest, TableUpdateRequest
- AliasCreateRequest, AliasUpdateRequest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add REST controllers for V3 Metadata CRUD API:
- DatabaseController: /graph/v3/databases/**
- TableController: /graph/v3/databases/{db}/tables/**
- AliasController: /graph/v3/databases/{db}/aliases/**

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix DdlStatus.result access (was content)
- Fix V2Field.type access (was dataType)
- Add Order conversion between V2 and V3
- Fix nullable handling with Mono.handle()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive unit tests for V3 Metadata conversion:
- Database conversion (ServiceEntity <-> DatabaseDescriptor)
- MutationMode conversion (V2 SYNC/ASYNC/IGNORE <-> V3 SYNC/ASYNC/DROP)
- DirectionType conversion (BOTH/OUT/IN)
- Storage conversion (datastore:// URI <-> Storage.HBase)
- Table conversion (LabelEntity -> TableDescriptor.Edge)
- Alias conversion (AliasEntity <-> AliasDescriptor)

All 21 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GitProperties may not be available when running in git worktree
environments where generateGitProperties task fails. Making it
optional allows the application to start without git metadata.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- TableControllerTest: 5 tests for table CRUD operations
- AliasControllerTest: 8 tests for alias CRUD operations
  - Includes deactivation before delete (required by V2 DDL service)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use assertThat with .value() instead of consumeWith
- Extract common request builders to private functions
- Use shorter variable names (db, table, alias, baseUri)
- Remove redundant assertions

133 lines removed, 80 lines added (same 17 tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove V2 API documentation
- Document V3 endpoints: Database, Table, Alias
- Update data types to V3 descriptors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change TableCreateRequest.storage from Storage to String
- Update V3CompatService to use storage string directly
- Update tests to use datastore://hbase/<tableName> format

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation

- Add import statement and Aside component for authentication caution
- Restructure all 14 endpoints with consistent format:
  - Endpoint, Parameters table, Request/Response Examples
- Add complete Data Model section with Kotlin data class definitions
- Use datastore:// URI format for storage examples

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Terminology Mapping (Service→Database, Label→Table)
- Add API Endpoint Mapping (14 endpoints)
- Add MutationMode Mapping (SYNC, ASYNC, IGNORE→DROP, DENY)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Security fixes:
- Add V3NameValidator for path variable validation (prevent injection)
- Add @validated to all V3 controllers
- Add Bean Validation annotations to request DTOs (@notblank, @pattern, @SiZe)
- Add HBase table name validation (reject empty names)

API improvements:
- Add DELETE endpoint for TableController
- Change DELETE response from 404 to 204 No Content on success

Test improvements:
- Add update tests for Database and Table controllers
- Add validation error tests (invalid names, injection attempts)
- Add empty HBase table name tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive compatibility tests for:
- Database: V2 create → V3 update, V3 create → V2 update
- Table: V2 create → V3 update, V3 create → V2 update
- Alias: V2 create → V3 update, V3 create → V2 update

Each test verifies consistency via both V2 and V3 GET endpoints.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace Kotlin data classes with raw JSON strings for requests and
use jsonPath assertions for responses. This makes the tests more
representative of actual E2E scenarios.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ests

Convert to parameterized tests using @CsvSource with pipe delimiter.
Test data is now explicit and tabular:

| scenario           | createApi | updateApi | name           | createValue   | updateValue   |
|--------------------|-----------|-----------|----------------|---------------|---------------|
| V2 create, V3 upd  | V2        | V3        | compat-db-v2v3 | created by v2 | updated by v3 |
| V3 create, V2 upd  | V3        | V2        | compat-db-v3v2 | created by v3 | updated by v2 |

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…data

Replace interpolated values with complete JSON strings in test data.
Now the exact request/response payloads are visible in the test table:

| scenario           | v2CreateReq                 | v3UpdateReq                  | expectedValue   |
|--------------------|-----------------------------|------------------------------|-----------------|
| V2 create, V3 upd  | {"desc": "created by v2"} | {"comment": "updated by v3"} | updated by v3   |

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add testFixtures(core) dependency to server module
- Use @ObjectSource with YAML format instead of @CsvSource
- Fix JSON format for V3 table create requests (lowercase type names)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use `|` (literal block scalar) to format JSON strings for better
readability in V2-V3 compatibility tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tests

Structure test data explicitly with:
- given: test preconditions (resource name)
- when: actions to perform (createApi, createReq, updateApi, updateReq)
- then: expected outcomes (expectedComment)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tion

- Each test function represents one 'when' scenario
- ObjectSource contains only 'given' and 'expected' JSON strings
- Common verify logic extracted to helper methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
More descriptive parameter names for request payloads.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Test data now shows complete flow:
- createReq: what goes into create API
- updateReq: what goes into update API
- expected: what we expect to see after

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
em3s and others added 5 commits February 5, 2026 23:48
- Use explicit name field instead of string interpolation
- Fix V2 Field format (add required nullable property)
- Remove invalid IN/BOTH direction tests (V2 HASH only supports OUT)
- Use unique property names to avoid conflicts
- English comments and documentation

Test cases:
- Database: 6 tests (V2→V3: 3, V3→V2: 3)
- Table: 7 tests (V2→V3: 4, V3→V2: 3)
- Alias: 6 tests (V2→V3: 3, V3→V2: 3)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
V3 Edge tables now use INDEXED type instead of deprecated HASH.
INDEXED supports all directions (OUT, IN, BOTH).

Changes:
- TableCreateRequest: default type = INDEXED
- V3MetadataConverter: use INDEXED for V3→V2 conversion
- V2V3CompatibilityTest: add IN/BOTH direction tests

Test cases: 23 total
- Database: 6 (V2→V3: 3, V3→V2: 3)
- Table: 11 (V2→V3: 6, V3→V2: 5)
- Alias: 6 (V2→V3: 3, V3→V2: 3)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TableDescriptor.MultiEdge to core module
- Add MultiEdge conversion in V3MetadataConverter
- Update V3CompatService to return polymorphic TableDescriptor
- Update TableController to handle polymorphic table types
- Add MultiEdge compatibility tests (V2 MULTI_EDGE <-> V3 multiEdge)

V2-V3 Type Mapping:
- V2 MULTI_EDGE = V3 multiEdge
- V2 INDEXED = V3 edge (existing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
V3 API design: POST (create) should not include resource name in URL.
The name should be in the request body.

Changes:
- POST /databases: name from body (DatabaseCreateRequest.database)
- POST /databases/{db}/tables: name from body (TableCreateRequest.table)
- POST /databases/{db}/aliases: name from body (AliasCreateRequest.alias)

PUT/GET/DELETE still use name in URL path.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change POST endpoints to exclude resource name from URL (REST design)
  - POST /graph/v3/databases (database name in body)
  - POST /graph/v3/databases/{database}/tables (table name in body)
  - POST /graph/v3/databases/{database}/aliases (alias name in body)
- Add MultiEdge table type documentation
- Add Label Type Mapping (V2 INDEXED/MULTI_EDGE/HASH to V3 edge/multiEdge)
- Document V3 alias constraint (same database only)
- Remove deprecated Korean V2 API documentation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@em3s em3s marked this pull request as ready for review February 6, 2026 01:58
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. module:server and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels Feb 6, 2026
em3s and others added 10 commits February 6, 2026 10:59
- Fix V2ServiceSerializationTest: add missing active field to expected DatabaseUpdateRequest
- Remove blanket onErrorResume in delete endpoints that swallowed all errors as 404
- Change createTable/updateTable return type from TableDescriptor.Edge to TableDescriptor<*>
- Fix docs: add missing Delete Table endpoint, fix response types for delete (204 No Content),
  fix PrimitiveType BYTES->OBJECT, fix TableCreateRequest schema type, fix broken Korean link,
  update API index version labels, add Delete Table to V2-V3 endpoint mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
V3 Edge tables are always INDEXED. Hardcode LabelType.INDEXED in
V3CompatService instead of exposing V2 internals in the request DTO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Widen schema type from ModelSchema.Edge to ModelSchema in TableCreateRequest
and TableUpdateRequest so both Edge and MultiEdge tables can be created/updated
through the V3 API. V3CompatService now derives LabelType and readOnly from
the schema type. Adds V3→V2 bidirectional compatibility tests for MultiEdge
and MultiEdge E2E tests in TableControllerTest. Also fixes a broken locale
link in the Korean API references page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Kotlin object-based test data with JSON strings using
@ObjectSourceParameterizedTest in DatabaseControllerTest,
TableControllerTest, and AliasControllerTest. Each parameterized case
runs the full CRUD lifecycle (create-get-update-deactivate-delete),
making test data reviewable as plain JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change V3 storage from polymorphic Storage object to plain URI string
  (datastore://{namespace}/{table})
- Delete Storage.kt sealed class (no longer needed)
- Remove storage conversion methods from V3MetadataConverter
- Update storage validation regex to support underscores, disallow hyphens
- Rename test parameters from v2/v3 to create/expected for consistency
- Use test_namespace in all test storage URIs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d functions

Each CRUD operation (create, update, deactivate, delete) is now a
separate @ObjectSourceParameterizedTest with its own @ObjectSource data,
so reviewers can understand each step by reading YAML alone.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… ordered CRUD functions

Apply the same pattern as AliasControllerTest: each CRUD operation
(create, update, deactivate, delete) is a separate @ObjectSourceParameterizedTest
with its own @ObjectSource data for independent reviewability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace @test, @CsvSource, @valuesource with @ObjectSourceParameterizedTest/@ObjectSource
for consistent data-driven testing across all V3 metadata tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@em3s
Copy link
Copy Markdown
Contributor Author

em3s commented Feb 6, 2026

No production impact

This PR is large in scope but has no impact on production.

  • V2 code untouched — not a single line of v2 package production code was modified
  • V3 is entirely new — all changes are confined to /graph/v3/ endpoints; they are never executed unless explicitly called
  • core changes are V3-onlyTableDescriptor, DatabaseUpdateRequest, etc. are V3 models; V2's ServiceEntity/LabelEntity remain unchanged

- Fix response example case: EDGE→edge, STRING→string (Jackson serialization)
- Make comment field required in TableCreateRequest and AliasCreateRequest
- Add DENY to MutationMode enum definition
- Add Index, IndexField, Group, GroupType model documentation
- Add Order enum and validation rules (name pattern, storage URI, comment length)
- Note PrimitiveType lowercase serialization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@em3s
Copy link
Copy Markdown
Contributor Author

em3s commented Feb 6, 2026

Document updated. Merging

@em3s em3s merged commit 751f913 into main Feb 6, 2026
10 checks passed
@em3s em3s self-assigned this Feb 6, 2026
@em3s em3s changed the title feat(server): implement V3 Metadata CRUD API Implement V3 Metadata CRUD API Feb 12, 2026
@em3s em3s deleted the feature/v3-metadata-api branch March 1, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant