SegmentServiceImpl.java
package com.deveagles.be15_deveagles_be.features.customers.command.infrastructure.service;
import com.deveagles.be15_deveagles_be.common.exception.BusinessException;
import com.deveagles.be15_deveagles_be.common.exception.ErrorCode;
import com.deveagles.be15_deveagles_be.features.customers.command.application.service.SegmentService;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.aggregate.Segment;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.aggregate.SegmentByCustomer;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.repository.SegmentByCustomerRepository;
import com.deveagles.be15_deveagles_be.features.customers.command.domain.repository.SegmentRepository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class SegmentServiceImpl implements SegmentService {
private final SegmentRepository segmentRepository;
private final SegmentByCustomerRepository segmentByCustomerRepository;
@Override
public void assignSegmentToCustomer(Long customerId, String segmentTag) {
log.info("고객 세그먼트 할당: customerId={}, segmentTag={}", customerId, segmentTag);
Optional<Segment> segmentOpt = segmentRepository.findBySegmentTag(segmentTag);
if (segmentOpt.isEmpty()) {
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "세그먼트를 찾을 수 없습니다: " + segmentTag);
}
Segment segment = segmentOpt.get();
// 중복 체크
if (segmentByCustomerRepository.existsByCustomerIdAndSegmentId(customerId, segment.getId())) {
log.debug("이미 할당된 세그먼트: customerId={}, segmentTag={}", customerId, segmentTag);
return;
}
// 세그먼트 할당
SegmentByCustomer segmentByCustomer =
SegmentByCustomer.builder().customerId(customerId).segmentId(segment.getId()).build();
segmentByCustomerRepository.save(segmentByCustomer);
log.info("고객 세그먼트 할당 완료: customerId={}, segmentTag={}", customerId, segmentTag);
}
@Override
public void assignSegmentsToCustomer(Long customerId, List<String> segmentTags) {
log.info("고객 다중 세그먼트 할당: customerId={}, segmentTags={}", customerId, segmentTags);
for (String segmentTag : segmentTags) {
try {
assignSegmentToCustomer(customerId, segmentTag);
} catch (Exception e) {
log.error(
"세그먼트 할당 실패: customerId={}, segmentTag={}, error={}",
customerId,
segmentTag,
e.getMessage());
}
}
}
@Override
public void removeSegmentFromCustomer(Long customerId, String segmentTag) {
log.info("고객 세그먼트 제거: customerId={}, segmentTag={}", customerId, segmentTag);
Optional<Segment> segmentOpt = segmentRepository.findBySegmentTag(segmentTag);
if (segmentOpt.isEmpty()) {
log.warn("세그먼트를 찾을 수 없음: segmentTag={}", segmentTag);
return;
}
Segment segment = segmentOpt.get();
if (!segmentByCustomerRepository.existsByCustomerIdAndSegmentId(customerId, segment.getId())) {
log.debug("할당되지 않은 세그먼트: customerId={}, segmentTag={}", customerId, segmentTag);
return;
}
segmentByCustomerRepository.deleteByCustomerIdAndSegmentId(customerId, segment.getId());
log.info("고객 세그먼트 제거 완료: customerId={}, segmentTag={}", customerId, segmentTag);
}
@Override
public void removeAllSegmentsFromCustomer(Long customerId) {
log.info("고객 전체 세그먼트 제거: customerId={}", customerId);
int deletedCount = segmentByCustomerRepository.findByCustomerId(customerId).size();
segmentByCustomerRepository.deleteByCustomerId(customerId);
log.info("고객 전체 세그먼트 제거 완료: customerId={}, count={}", customerId, deletedCount);
}
@Override
public void removeRiskSegmentsFromCustomer(Long customerId) {
log.info("고객 위험 세그먼트 제거: customerId={}", customerId);
List<SegmentByCustomer> riskSegments =
segmentByCustomerRepository.findRiskSegmentsByCustomerId(customerId, "risk");
for (SegmentByCustomer segmentByCustomer : riskSegments) {
segmentByCustomerRepository.deleteByCustomerIdAndSegmentId(
customerId, segmentByCustomer.getSegmentId());
}
log.info("고객 위험 세그먼트 제거 완료: customerId={}, count={}", customerId, riskSegments.size());
}
@Override
@Transactional(readOnly = true)
public List<Segment> getCustomerSegments(Long customerId) {
log.debug("고객 세그먼트 조회: customerId={}", customerId);
List<SegmentByCustomer> segmentByCustomers =
segmentByCustomerRepository.findByCustomerId(customerId);
return segmentByCustomers.stream()
.map(sbc -> segmentRepository.findById(sbc.getSegmentId()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
@Override
@Transactional(readOnly = true)
public List<Long> getCustomerIdsBySegmentTag(String segmentTag) {
log.debug("세그먼트별 고객 ID 조회: segmentTag={}", segmentTag);
return segmentByCustomerRepository.findCustomerIdsBySegmentTag(segmentTag);
}
@Override
@Transactional(readOnly = true)
public List<Long> getCustomerIdsBySegmentTags(List<String> segmentTags) {
log.debug("다중 세그먼트별 고객 ID 조회: segmentTags={}", segmentTags);
return segmentByCustomerRepository.findCustomerIdsBySegmentTags(segmentTags);
}
@Override
public Segment createSegmentIfNotExists(
String segmentTag, String segmentTitle, String colorCode) {
log.info("세그먼트 생성 (존재하지 않는 경우): segmentTag={}, segmentTitle={}", segmentTag, segmentTitle);
Optional<Segment> existingSegment = segmentRepository.findBySegmentTag(segmentTag);
if (existingSegment.isPresent()) {
log.debug("이미 존재하는 세그먼트: segmentTag={}", segmentTag);
return existingSegment.get();
}
Segment newSegment =
Segment.builder()
.segmentTag(segmentTag)
.segmentTitle(segmentTitle)
.colorCode(colorCode)
.build();
Segment savedSegment = segmentRepository.save(newSegment);
log.info("새 세그먼트 생성 완료: segmentTag={}, id={}", segmentTag, savedSegment.getId());
return savedSegment;
}
@Override
public void initializeDefaultSegments() {
log.info("기본 세그먼트 초기화 시작");
// 고객 생애주기 세그먼트
createSegmentIfNotExists("new", "신규 고객", "#00BFFF");
createSegmentIfNotExists("growing", "성장 고객", "#32CD32");
createSegmentIfNotExists("loyal", "충성 고객", "#FFD700");
createSegmentIfNotExists("vip", "VIP 고객", "#FF69B4");
createSegmentIfNotExists("inactive", "비활성 고객", "#808080");
// 위험 세그먼트
createSegmentIfNotExists("churn_risk_high", "고위험 이탈", "#FF4444");
createSegmentIfNotExists("churn_risk_medium", "중위험 이탈", "#FF8800");
createSegmentIfNotExists("churn_risk_low", "저위험 이탈", "#FFAA00");
// 특별 케어 세그먼트
createSegmentIfNotExists("vip_attention_needed", "VIP 관심 필요", "#8844FF");
createSegmentIfNotExists("pattern_break_detected", "패턴 이상 감지", "#FF6600");
createSegmentIfNotExists("reactivation_needed", "재활성화 필요", "#FF0000");
createSegmentIfNotExists("first_visit_follow_up", "첫 방문 팔로업", "#0088FF");
log.info("기본 세그먼트 초기화 완료");
}
}