본문으로 건너뛰기

홈페이지 서비스

이 페이지는 landing-page 설정, 게시 상태, customId 기준 공개 조회를 저장하는 tenant 범위 모듈 lumie-backend/modules/homepage의 레퍼런스입니다.

소스 경로

Path역할
lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/adapter/in/web/HomepageController.java공개 및 인증 homepage endpoint
lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/application/service/HomepageQueryService.java현재 tenant 조회와 RLS 재진입이 필요한 공개 customId 조회
lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/application/service/HomepageCommandService.javaupsert 및 publish/unpublish 명령
lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/domain/entity/HomepageConfig.javatenant당 하나의 homepage config aggregate
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sqlhomepage_config의 table 생성과 RLS
lumie-backend/app/src/main/resources/db/migration/public/V46__fix_homepage_config_per_tenant_singleton.sqltenant당 singleton 계약을 복구하는 후속 migration

공개 인터페이스

Endpoint목적
GET /v1/homepage현재 tenant의 homepage config를 반환하며, 아직 저장된 적이 없으면 일시적인 기본 config를 반환
PUT /v1/homepage인증된 tenant에 대해 homepage config를 upsert하며 AuthorizationGuard.requireOwner()가 OWNER 권한을 강제
POST /v1/homepage/publish저장된 config의 published 플래그를 전환, OWNER 전용
GET /v1/homepage/public/by-custom-id/{customId}tenant customId 기준 익명 공개 조회

모듈 경계

Dependency역할
TenantLookupPort모듈이 tenant context에 재진입하기 전에 customId로 tenant를 해석
TenantContextHolder.withinContext(...)PostgreSQL RLS가 올바른 homepage row를 볼 수 있도록 slug와 tenant ID를 모두 복원
AuthorizationGuardhomepage 쓰기와 publish 전환을 OWNER 사용자로 제한

homepage 모듈은 JSON section과 publish 상태만 저장합니다. 파일 바이너리 자체를 직접 소유하지는 않으며, image ID와 URL은 homepage JSON 문서 내부의 payload field로 들어갑니다.

집계와 테이블

Aggregate참고
HomepageConfigtemplateId, templateVersion, sections, published를 가진 tenant 범위 singleton

HomepageConfig.defaultConfig()는 영속 row가 아닙니다. tenant가 아직 설정을 저장한 적이 없을 때 인증된 GET /v1/homepage에만 반환되는 일시적 fallback 객체입니다.

런타임 흐름

계약 참고 사항

공개 읽기 경로는 현재 published 플래그를 강제하지 않습니다. 컨트롤러 주석은 여전히 그렇게 설명하고 있지만, 실제 코드는 다릅니다.

// lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/application/service/HomepageQueryService.java
@Transactional(readOnly = true)
public Optional<HomepageConfigResponse> findCurrent() {
// The homepage is always public once saved — no `published` gate.
return homepageConfigPersistencePort.find()
.map(config -> HomepageConfigResponse.from(config, objectMapper));
}

즉, 현재 런타임 계약은 다음과 같습니다.

  • 인증된 GET /v1/homepage는 row가 없으면 기본 config를 반환합니다.
  • 익명 GET /v1/homepage/public/by-custom-id/{customId}는 저장된 config가 있으면 published 값과 상관없이 반환합니다.
  • 익명 읽기가 404를 반환하는 경우는 tenant lookup이 실패했거나 homepage row가 전혀 없을 때뿐입니다.

예시 계약

이 예시는 HomepageController, UpdateHomepageConfigRequest, PublishHomepageRequest, HomepageConfigResponse, HomepageQueryService에서 직접 가져왔습니다.

홈페이지 설정 저장

PUT /v1/homepage
Content-Type: application/json

{
"templateId": "solo-instructor",
"templateVersion": 1,
"sections": {
"hero": {
"headline": "Learn with Lumie"
}
}
}
HTTP/1.1 200 OK

{
"id": 1,
"templateId": "solo-instructor",
"templateVersion": 1,
"published": false,
"sections": {
"hero": {
"headline": "Learn with Lumie"
}
},
"updatedAt": "<timestamp>"
}

공개 홈페이지 응답

GET /v1/homepage/public/by-custom-id/acme
HTTP/1.1 200 OK

{
"id": 1,
"templateId": "solo-instructor",
"templateVersion": 1,
"published": false,
"sections": {
"hero": {
"headline": "Learn with Lumie"
}
},
"updatedAt": "<timestamp>"
}

published: false 공개 예시는 의도된 것입니다. HomepageQueryService.Tx.findCurrent()가 publish gate를 제거했기 때문에, 저장된 row는 존재하는 즉시 공개됩니다.

실패, 재시도, 관측성

  • HomepageCommandService.publish(...){}[] 같은 빈 JSON을 게시하려는 시도를 거부합니다.
  • 공개 읽기는 반드시 HomepageQueryService.Tx를 통한 실제 Spring proxy transaction boundary를 거쳐야 합니다. 그렇지 않으면 RLS aspect가 app.tenant_id를 바인딩하지 못합니다.
  • 컨트롤러 주석과 오래된 요약은 여전히 게시된 homepage만 공개된다고 설명하지만, 현재 application service는 그 gate를 명시적으로 제거했습니다.
  • 이 모듈은 주로 application log를 통해 상태 변화를 드러내며, queue, retry worker, 외부 callback 경로는 없습니다.

검증

cd lumie-backend
./gradlew :modules:homepage:test
./gradlew :modules:homepage:test --tests '*Homepage*'

예상 성공 신호:

  • Gradle이 BUILD SUCCESSFUL로 종료되고, homepage 모듈 테스트가 current-config, publish, public-read 경로를 다룹니다.
  • HomepageQueryService.Tx.findCurrent()가 여전히 published를 검사하지 않고 저장된 row를 반환하며, 이는 이 페이지의 예시 계약과 일치합니다. 비록 HomepageController 주석과 테스트는 여전히 published-only 경로를 설명하더라도 그렇습니다.

관련 페이지