홈페이지 서비스
이 페이지는 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.java | upsert 및 publish/unpublish 명령 |
lumie-backend/modules/homepage/src/main/java/com/lumie/homepage/domain/entity/HomepageConfig.java | tenant당 하나의 homepage config aggregate |
lumie-backend/app/src/main/resources/db/migration/public/V18__rls_baseline.sql | homepage_config의 table 생성과 RLS |
lumie-backend/app/src/main/resources/db/migration/public/V46__fix_homepage_config_per_tenant_singleton.sql | tenant당 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를 모두 복원 |
AuthorizationGuard | homepage 쓰기와 publish 전환을 OWNER 사용자로 제한 |
homepage 모듈은 JSON section과 publish 상태만 저장합니다. 파일 바이너리 자체를 직접 소유하지는 않으며, image ID와 URL은 homepage JSON 문서 내부의 payload field로 들어갑니다.
집계와 테이블
| Aggregate | 참고 |
|---|---|
HomepageConfig | templateId, 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 경로를 설명하더라도 그렇습니다.