๊นํ๋ธ์ ๋ค๋ฅธ ์ฌ๋๋ค์ด ์์ฑํ ์ฝ๋๋ค์ ๋ณด๋ฉด ๋ฐ๋ก ์ ์ ์๋ฏ์ด, ์ฌ๋๋ง๋ค ๋ชจ๋ ์ํคํ
์ฒ ์ค๊ณ ๋ฐฉ๋ฒ๊ณผ, ์ฝ๋ ๊ตฌ์กฐ ์ค๊ณ๋ ๋ชจ๋ ๋ค๋ฅด๋ค. ํ์ฌ Dataracy๋ผ๋ ํ๋ซํผ ๊ฐ๋ฐ์์ ๋๋ ์ด๋ป๊ฒ ์ค๊ณ๋ฅผ ํด์ผํ ์ง ๋ง์ ๊ณ ๋ฏผ์ ํ๋ฉฐ ๊ณ์ํด์ ๋ฐ๊พธ์ด ๋๊ฐ๊ณ ์๋ค.
์ฒ์์๋ Controller, Service, Domain, Repository ์ด๋ ๊ฒ ๊ณ์ธต๋ณ๋ก ์ค๊ณํ์๊ณ , ๋ฐ๋ก ์ด์ ํ๋ก์ ํธ์ธ HitZone์์๋ DDD ๊ตฌ์กฐ์ ๋ํด์ ๊ณ ๋ฏผ์ ํด๋ณด์๋๋ฐ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์์ํ๋ฉฐ DDD์ ํฅ์ฌ๊ณ ๋ ์ํคํ
์ฒ์ ๋ํด์ ์ ํ๊ฒ ๋์ด ์ด๋ฒ ํ๋ก์ ํธ์ ๋์๊ฒ ์ปค์คํ
ํ์ฌ ์ ์ฉํด๋ณด์๋ค.
๐ค ์ ๋ ์ด์ด๋์์ ํฌํธ/์ด๋ํฐ๋ก ์ ํํ๋๊ฐ
โ ๊ธฐ์กด ๋ ์ด์ด๋ ์ํคํ
์ฒ์ ํ๊ณ
- ๊ท์น ํฌ์ & ๊ฒฐํฉ๋ ์ฆ๊ฐ
Service๊ฐ DTO ์กฐ๋ฆฝยท์ฟผ๋ฆฌยท์ธ๋ถ ํธ์ถ๊น์ง ๋ชจ๋ ๋ ์์
โ ์๊ฐ์ด ์ง๋ ์๋ก ๋๋ฉ์ธ ๊ท์น์ด I/O์ ์ ์๋ ์ ์๋ค.
- ๋ณ๊ฒฝ ๋ด์ฑ ๋ถ์กฑ
์ ์ฅ์/๊ฒ์/์บ์ ์ ๋ต์ ๋ฐ๊พธ๋ฉด ControllerยทService๊น์ง ์ฐ์ ์์ ํ์
- ์ฝ๊ธฐ/์ฐ๊ธฐ ๊ด์ฌ์ฌ ์ถฉ๋
์ฝ๊ธฐ(์กฐ์ธ/ํ๋ก์ ์
/์บ์)์ ์ฐ๊ธฐ(ํธ๋์ญ์
)๊ฐ ๊ฐ์ ๋ ์ด์ด์์ ์์
- ๋๋ฆฌ๊ณ ๋ถ์์ ํ ํ
์คํธ
Service ํ
์คํธ๊ฐ ํตํฉ ํ
์คํธํ๋์ด ๋๋ฆผ
๐ฏ ์ ํ ๋ชฉํ
- ์ฝ์ด(๋๋ฉ์ธ/์ ์ค์ผ์ด์ค)๋ฅผ ๊ตฌํ ๊ธฐ์ ๋ก๋ถํฐ ๊ฒฉ๋ฆฌ : ๋๋ฉ์ธ/์ ์ค์ผ์ด์ค์์ ํ๋ ์์ํฌยทI/O ์ ๊ฑฐ
- ๋จ๋ฐฉํฅ ์์กด : Adapters โ Application โ Domain
- ๊ฒฝ๊ณ-ํ์ ๋งคํ : ๋ณํ์ WebโApp, AppโDomain(๋ช
๋ น/์๋ต), DomainโPersistence(JPA/Query), AppโIndex/Cache(Redis/ES) ๊ฒฝ๊ณ์์๋ง ์ํํ๋ค.
๐ ํ ์ค ์์ฝ:
โ๊ธฐ์ ์ด ๋ฐ๋์ด๋ ๊ท์น์ ์ ๋ฐ๋๊ฒโ โ ๊ท์น์ ๋๋ฉ์ธ/์ ์ค์ผ์ด์ค, ๊ธฐ์ ์ ์ด๋ํฐ.
๋์ค์ ๊ตฌํํ ๊ธฐ์ ์ด ๋ฐ๋๊ฑฐ๋ ์ถ๊ฐ๋์ด๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ํฅ์ ๋ฐ์ง ์๋๋ค.
๐งฉ ํต์ฌ ๊ฐ๋
์ ๋ฆฌ: DDD & ํฅ์ฌ๊ณ ๋
๐ DDD (Domain-Driven Design)
- Ubiquitous Language : ํ์ด ์ฐ๋ ์ฉ์ด๋ฅผ ์ฝ๋์ ๊ทธ๋๋ก ํฌ์
- ๋ชจ๋ธ ์์ง : ์ํฐํฐ/VO/๋๋ฉ์ธ ์๋น์ค/๋ถ๋ณ์ ๋ฑ์ผ๋ก ๊ท์น์ ์ฝ๋์ ๊ณ ์
- Bounded Context : ์๋ฏธ ์ถฉ๋ ์๋ ๊ฒฝ๊ณ ๋จ์๋ก ๋๋์ด ๋
๋ฆฝ ์งํ
๐ ํฅ์ฌ๊ณ ๋ (Ports & Adapters)
- Port(๊ณ์ฝ) : ์ฝ์ด๊ฐ ์ธ๋ถ์ ์ํธ์์ฉํ๋ ์ถ์ ์ธํฐํ์ด์ค
- Adapter(๊ตฌํ) : Port๋ฅผ ๊ตฌํํด UI/DB/์บ์/๊ฒ์ ๊ฐ์ ๊ธฐ์ ์ ์ฐ๊ฒฐ
- Driving Adapter (์
๋ ฅ/์์) : Controller ๊ฐ์ UI ๊ณ์ธต โ ์ฝ์ด ํธ์ถ
- Driven Adapter (์ถ๋ ฅ/ํ์) : JPA/Query/Redis/Index โ ์ฝ์ด์ ์์ฒญ ์ํ
๐ ์ Controller๋ ์ด๋ํฐ์ธ๊ฐ?
HTTP ์์ฒญ์ ๋ฐ์ Port In(UseCase)์ ํธ์ถํ๋ "๊ตฌํ์ฒด"์ด๊ธฐ ๋๋ฌธ.
์ฆ, Controller = ์ฝ์ด๋ฅผ ํธ์ถํ๋ ์
๋ ฅ ์ด๋ํฐ.
๐ ๊ฐ ๊ณ์ธต์ ๋ฌด์์ด ์์ด์ผ ํ๋๊ฐ (๊ฐ์ด๋๋ผ์ธ)
๐ Domain
- ๋๋ฉ์ธ ๋ชจ๋ธ, VO, ENUM, ๋๋ฉ์ธ ์๋น์ค,
- ๋ถ๋ณ์, ๋๋ฉ์ธ ์์ธ
โก๏ธ ํ๋ ์์ํฌ ๋ถ๊ฐ
โ๏ธ Application
- Port In (UseCase)
- Application Service
- ์ค์ผ์คํธ๋ ์ด์
: ๋๋ฉ์ธ ๊ท์น์ ์ ์ฉํ๊ธฐ ์ํด ์ด๋ค ์์๋ก, ์ด๋ค ํฌํธ๋ฅผ ๋ถ๋ฌ์ผ ํ๋์ง ์กฐํฉํ๋ ๊ฒ์ด ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค์ ์ฑ
์
- Port Out (์ธ๋ถ I/O ๊ณ์ฝ)
- App DTO, App Mapper
โก๏ธ ์ธ๋ถ I/O ๊ตฌํ ๋ฐฉ๋ฒ์ ๋ชจ๋ฆ
๐ Adapters
- Web: Controller, Web DTO, Web Mapper
- Query: QueryDSL Adapter (์กฐ๊ฑด/์กฐ์ธ/ํ๋ก์ ์
/์ ๋ ฌ/ํ์ด์ง)
- JPA: Entity, JpaRepository, Impl, Entity Mapper (์์ โ ๋๋ฉ์ธ)
- Redis: ์ธ์ฆ/๋ณด์ ๊ด๋ จ ํ ํฐ ๊ด๋ฆฌ (์: Refresh Token ์ ์ฅยท์กฐํ, ๋ง๋ฃ TTL ์ ์ฉ), ์กฐํ์/์ค๋ณต๋ฐฉ์ง TTL
- Index/Search: ๊ฒ์ ์ธ๋ฑ์ฑ/์
๋ฐ์ดํธ, ์กฐํ ์ต์ ํ (ES/Painless Script ๋ฑ)
๐ ์ค๊ณ ํ๋ก: ๊ตฌ์กฐ๋ & ์ํ์ค
๐ ๊ตฌ์กฐ๋ (Mermaid)

๐ ์ํ์ค Diagram
ํ๋ก์ ํธ ์ธ๋ถ ์ ๋ณด ์กฐํ API๋ฅผ ๊ฐ์
ํ๋ก์ ํธ ์กฐํ ์ ์ธ๋ถ ์ ๋ณด ๋ฐํ ๋ฐ ํ๋ก์ ํธ ๊ฒ์๊ธ ์กฐํ์ ์ฆ๊ฐ ๋ก์ง
๐ค ์ฌ์ฉ์ ->> ๐ ์น ์ปจํธ๋กค๋ฌ: GET /api/v1/projects/{id}
๐ ์น ์ปจํธ๋กค๋ฌ ->> ๐ฏ ์
๋ ฅํฌํธ(์ ์ค์ผ์ด์ค): getProjectDetail(id, userId, viewerId) ํธ์ถ
๐ฏ ์
๋ ฅํฌํธ(์ ์ค์ผ์ด์ค) ->> โ๏ธ ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค: ProjectReadService ์คํ
โ๏ธ ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค ->> ๐งฉ ๋๋ฉ์ธ๊ท์น: ๋ถ๋ณ์/๊ท์น ํ์ธ
โ๏ธ ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค ->> ๐ ๏ธ ์กฐํ ์ถ๋ ฅํฌํธ: findProjectWithDataById(id) ์์ฒญ
๐ ๏ธ ์กฐํ ์ถ๋ ฅํฌํธ ->> ๐ ์ฟผ๋ฆฌ ์ด๋ํฐ: ์กฐ๊ฑดยท์กฐ์ธยทํ๋ก์ ์
ยท์ ๋ ฌยทํ์ด์ง ์ฒ๋ฆฌ
๐ ์ฟผ๋ฆฌ ์ด๋ํฐ -->> ๐ ๏ธ ์กฐํ ์ถ๋ ฅํฌํธ: Project + DataIds ๋ฐํ
โ๏ธ ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค ->> ๐ ๏ธ ๋ ๋์ค(์บ์) ์ถ๋ ฅํฌํธ: increaseViewCount(id, viewerId, "PROJECT") ์์ฒญ
๐ ๏ธ ๋ ๋์ค(์บ์) ์ถ๋ ฅํฌํธ ->> โก ๋ ๋์ค์ด๋ํฐ: โ setIfAbsent + increment ์คํ
โ๏ธ ์ ํ๋ฆฌ์ผ์ด์
์๋น์ค -->> ๐ ์น์ปจํธ๋กค๋ฌ: Application DTO ๋ฐํ
๐ ์น์ปจํธ๋กค๋ฌ -->> ๐ค ์ฌ์ฉ์: Web DTO ๋ณํ ํ 200 OK ์๋ต
๐ ๊ตฌํ ์์: ํ๋ก์ ํธ ์์ธ ์กฐํ (์กฐํ์ยท์ข์์ ํฌํจ)
๐ Web Layer (Driving Adapter)
๐ ์ญํ
- HTTP ์ง์
์ (Controller).
- ์์ฒญ ํค๋/ํ๋ผ๋ฏธํฐ๋ฅผ ํ์ฑํ๊ณ , Port In(UseCase) ์ ํธ์ถํ๋ค.
- Web DTO โ App DTO ๋ณํ์ ์ฑ
์์ง๋ค.
- ์ฆ, Web Layer๋ UI ๊ณ์ฝ๋ง ๋ด๋นํ๋ค.
๐ป ํต์ฌ ์ฝ๋
@RestController
@RequiredArgsConstructor
public class ProjectReadController implements ProjectReadApi {
private final GetProjectDetailUseCase getProjectDetailUseCase;
@Override
public ResponseEntity<SuccessResponse<ProjectDetailWebResponse>> getProjectDetail(
HttpServletRequest request, HttpServletResponse response, Long projectId) {
Long userId = extractHeaderUtil.extractAuthenticatedUserIdFromRequest(request);
String viewer = extractHeaderUtil.extractViewerIdFromRequest(request, response);
ProjectDetailResponse appDto =
getProjectDetailUseCase.getProjectDetail(projectId, userId, viewer);
ProjectDetailWebResponse webDto = projectReadWebMapper.toWebDto(appDto);
return ResponseEntity.ok(SuccessResponse.of(ProjectSuccessStatus.GET_PROJECT_DETAIL, webDto));
}
}
๐ ์ค๋ช
- Web Layer๋ ๋น์ฆ๋์ค ๋ก์ง์ด ์ ํ ์๋ค.
- ์ค์ง ์์ฒญ/์๋ต ๋ณํ๊ณผ ํฌํธ ํธ์ถ, ์ฟ ํค์ ํค๋ ์ค์ ์๋ง ์ง์คํ๋ค.
- ๋ฐ๋ผ์ ํ
์คํธํ๊ธฐ ์ฝ๊ณ , UI ๊ณ์ฝ(API ์คํ) ๋ณ๊ฒฝ์๋ Application์ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
โ๏ธ Application Layer (Port + Service + Tx/Orchestration)
๐ Ports (๊ณ์ฝ)
๐ ์ญํ
- Service์ Adapter ๊ฐ์ ์ถ์ ๊ณ์ฝ.
- Adapter ๊ต์ฒด ๊ฐ๋ฅ์ฑ์ ํ๋ณดํ๋ค. (์: JPA โ MyBatis๋ก ๋ณ๊ฒฝํด๋ Service๋ ๊ทธ๋๋ก)
๐ป ํต์ฌ ์ฝ๋
public interface FindProjectPort {
Optional<ProjectWithDataIdsResponse> findProjectWithDataById(Long projectId);
}
public interface CacheProjectViewCountPort {
void increaseViewCount(Long targetId, String viewerId, String targetType);
}
๐ ์ค๋ช
- Service๋ Port๋ง ๋ฐ๋ผ๋ณธ๋ค.
- ๊ตฌํ์ฒด๋ Adapter์์ ์ ๊ณตํ๋ค.
- Port = โApplication์ ์์กด์ฑ ์ญ์ โ์ ๊ตฌํํ๋ ํต์ฌ.
๐ Service
๐ ์ญํ
- UseCase ๊ตฌํ์ฒด์ด์ ํธ๋์ญ์
๊ฒฝ๊ณ.
- ์ฌ๋ฌ Port๋ฅผ ์กฐํฉํ์ฌ ๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ๋ค.
- ๋๋ฉ์ธ ๋ถ๋ณ์ ๊ฒ์ฌ, ๋ฉฑ๋ฑ ์ฒ๋ฆฌ ํธ๋ฆฌ๊ฑฐ ๋ฑ ์ค์ผ์คํธ๋ ์ด์
์ฑ
์๋ง ๊ฐ์ง๋ค.
๐ป ํต์ฌ ์ฝ๋
@Service
@RequiredArgsConstructor
public class ProjectReadService implements GetProjectDetailUseCase {
private final FindProjectPort findProjectPort;
private final ValidateTargetLikeUseCase likeUseCase;
private final CacheProjectViewCountPort viewCountPort;
@Override @Transactional(readOnly = true)
public ProjectDetailResponse getProjectDetail(Long projectId, Long userId, String viewerId) {
var res = findProjectPort.findProjectWithDataById(projectId)
.orElseThrow(() -> new ProjectException(ProjectErrorStatus.NOT_FOUND_PROJECT));
boolean isLiked = (userId != null) &&
likeUseCase.hasUserLikedTarget(userId, projectId, TargetType.PROJECT);
viewCountPort.increaseViewCount(projectId, viewerId, "PROJECT");
return projectDetailDtoMapper.toResponseDto(res.project(), isLiked, );
}
}
๐ ์ค๋ช
- Service๋ โ์ด ์์
์ ์ํด ์ด๋ค Port๋ฅผ ํธ์ถํด์ผ ํ๋๊ฐ?โ๋ฅผ ๊ณ ๋ฏผํ๋ค.
- ๋ชจ๋ I/O๋ Port ๋ค๋ก ์จ๊ธฐ๋ฏ๋ก, Infra ์ธ๋ถ์ฌํญ(DB, Redis, ES ๋ฑ) ์ Application์ด ๋ชจ๋ฅธ๋ค.
- @Transactional์ ๋ถ์ฌ ํธ๋์ญ์
๋จ์(์ฝ๊ธฐ/์ฐ๊ธฐ)๋ฅผ ๋ณด์ฅํ๋ค.
๐ Adapter - Query (์ฝ๊ธฐ ์ต์ ํ)
๐ ์ญํ
- ์ค์ QueryDSL๋ก DB์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ค. (Jpa๋ Persistence Adapter)
- ์กฐ๊ฑด/์กฐ์ธ/ํ๋ก์ ์
/ํ์ด์ง์ ์ ๋ถ Adapter ์์์ ๋๋ธ๋ค.
- Application์ โ์ฟผ๋ฆฌ ์ธ๋ถโ๋ฅผ ๋ชจ๋ฅธ ์ฑ ๋จ์ DTO๋ ์ํฐํฐ์์ ๋ณํ๋ ๋๋ฉ์ธ ๋ชจ๋ธ๋ง ๋ฐ๋๋ค.
๐ป ํต์ฌ ์ฝ๋
@Repository
@RequiredArgsConstructor
public class ReadProjectQueryDslAdapter implements FindProjectPort {
private final JPAQueryFactory q;
@Override
public Optional<ProjectWithDataIdsResponse> findProjectWithDataById(Long id) {
ProjectEntity e = q.selectFrom(QProjectEntity.projectEntity)
.where(QProjectEntity.projectEntity.id.eq(id),
QProjectEntity.projectEntity.isDeleted.isFalse())
.fetchOne();
if (e == null) return Optional.empty();
return Optional.of(new ProjectWithDataIdsResponse(ProjectEntityMapper.toMinimal(e), List.of()));
}
}
๐ ์ค๋ช
- DB ์ฟผ๋ฆฌ ์ต์ ํ๋ Adapter์์๋ง ๊ณ ๋ฏผํ๋ค.
- Application์ โfindProjectWithDataByIdโ๋ผ๋ ๊ณ์ฝ๋ง ์๋ฉด ๋๋ค.
- ์ ์ง๋ณด์์ฑ์ด ๋์์ง๊ณ , DB ๊ต์ฒด(MySQL โ PostgreSQL) ์ Application์ ๋ณ๊ฒฝ์ด ์๋ค.
๐ Adapter - Redis (์กฐํ์ ๋ฉฑ๋ฑ)
๐ ์ญํ
- TTL(5๋ถ) ๊ธฐ๋ฐ์ผ๋ก ์กฐํ์ ์ค๋ณต ๋ฐฉ์ง.
- ๊ฐ์ viewer๊ฐ 5๋ถ ๋ด ์ฌ๋ฌ ๋ฒ ์กฐํํด๋ 1ํ๋ก๋ง ์นด์ดํธ.
- ์๋ก๊ณ ์นจ/๋ด ์คํ์ดํฌ๋ฅผ ์์ถฉ.
๐ป ํต์ฌ ์ฝ๋
@Component
@RequiredArgsConstructor
public class ProjectViewCountRedisAdapter implements CacheProjectViewCountPort {
private final StringRedisTemplate redis;
@Override
public void increaseViewCount(Long projectId, String viewerId, String targetType) {
if (viewerId == null) return;
String dedupKey = "viewDedup:%s:%s:%s".formatted(targetType, projectId, viewerId);
Boolean first = redis.opsForValue().setIfAbsent(dedupKey, "1", Duration.ofMinutes(5));
if (Boolean.TRUE.equals(first)) {
String countKey = "viewCount:%s:%s".formatted(targetType, projectId);
redis.opsForValue().increment(countKey);
}
}
}
๐ ์ค๋ช
- Redis๋ ์งง์ TTL ๋ฉฑ๋ฑ ๋ณด์ฅ์ ํนํ๋ ์ ์ฅ์.
- ๋ณธ ์์์์๋ โ์กฐํ์ ์ฆ๊ฐโ๋ฅผ ์์ ์ ์ผ๋ก ๋ค๋ฃจ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
- ๋์ค์ RDB์ ๋๊ธฐํ ์์๋ ์์ ์ ์ด๋ค. (Scheduler Or Batch)
๐๏ธ JPA Entity & Mapper
๐ ์ญํ
- DB ํ
์ด๋ธ ๋งคํ ์ ์ฉ.
- ๋น์ฆ๋์ค ๋ก์ง ์์.
- Domain ๊ฐ์ฒด์ ๋ถ๋ฆฌํ์ฌ DB์ ๊ฒฐํฉํ Adapter ์ ์ฉ์ผ๋ก ์ฌ์ฉ.
๐ป ํต์ฌ ์ฝ๋
@Entity @Table(name = "project")
@Where(clause = "is_deleted = false")
public class ProjectEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private Long commentCount = 0L;
private Long likeCount = 0L;
private Long viewCount = 0L;
...
}
๐ ์ค๋ช
- Entity๋ ์ค์ง DB์ ๊ฒฐํฉ๋ ORM์ผ๋ก DB์์ ์์ฑ, ์์ , ์กฐํ, ์ญ์ ๋ฑ์ ์์
์ ์ฌ์ฉ๋๋ค.
- Mapper๊ฐ Entity <-> Domain ๋ณํ์ ๋ด๋นํ์ฌ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ๋ค.
ํต์ฌ: ์์ ํํ(JPA) โ ๋๋ฉ์ธ ๋ณํ์ Entity Mapper์์๋ง. ๋ชจ๋ธ ์นจ์ ๋ฐฉ์ง.
๐ ์ ๋ฆฌ
- Web Layer: ์์ฒญ/์๋ต ๊ณ์ฝ, Port In ํธ์ถ
- Application: Txยท์ค์ผ์คํธ๋ ์ด์
, Port Out ํธ์ถ
- Ports: ๊ณ์ฝ ์ ์, Adapter ๊ต์ฒด ๊ฐ๋ฅ์ฑ ๋ณด์ฅ
- Adapter: Web, Db, Persistence, Query, Redis ๋ฑ ์ค์ ๊ตฌํ์ ๋ด๋น
๐ ์ด ๊ตฌ์กฐ ๋๋ถ์
- UI/API ๋ณ๊ฒฝ ์ Application์ ์ํฅ ์์
- Infra ๊ต์ฒด(JPA โ MyBatis, Redis โ Memcached)์๋ Service๋ ๊ทธ๋๋ก
- ๊ฐ ๊ณ์ธต์ ์๊ธฐ ์ฑ
์๋ง ์ํ
โจ ํต์ฌ ์์ฝ
โ
์ป๋ ์ด์
- ๐ ๋ณ๊ฒฝ ๋ด์ฑ: ์ ์ฅ์ยท๊ฒ์ยท์บ์ ๊ต์ฒด ์ ์ด๋ํฐ๋ง ์์ , ์ฝ์ด(์ ์ค์ผ์ด์คยท๋๋ฉ์ธ)๋ ์์ ์
- ๐ ๊ฐ๋
์ฑยท์ ์ง๋ณด์์ฑ: ๊ณ์ธต ์ญํ ์ด ๋ถ๋ฆฌ๋ผ ์ฝ๋ ์๋๊ฐ ์ ๋ช
ํ๊ณ ์จ๋ณด๋ฉ ์ฉ์ด
- โก ์ฑ๋ฅยท์ด์ ์ต์ ํ: Query Adapter์ ์ฑ๋ฅ ํ๋ ์ง์ค, Redis TTL๋ก ์ค๋ณต ์์ฒญ ๋ฐฉ์ด
โ๏ธ ๊ณ ๋ ค์ฌํญ
- ์ด๊ธฐ์ PortยทMapper๊ฐ ๋ง์๋ณด์ฌ ์ง์
์ฅ๋ฒฝ์ด ์์
- CQRS-lite๋ ํ์ํ ๊ณณ๋ง ์ ์ฉํด์ผ ํ๋ฉฐ, ๊ณผ๋ํ ๋ถ๋ฆฌ๋ ์คํ๋ ค ๋ณต์ก๋โ
- ์์กด ๋ฐฉํฅ: Adapters โ Application โ Domain
- Controller๋ Port In๋ง ํธ์ถ (Repo ์ง์ ์ ๊ทผ ๊ธ์ง)
- Service๋ Tx/์ค์ผ์คํธ๋ ์ด์
์ ์ฉ, ๋ชจ๋ I/O๋ Port Out ๊ฒฝ์
- Query/์
์ํธ/TTL/๋ฉฑ๋ฑ์ Adapter ๋ด๋ถ ์ ์ฉ
- ๊ฒฝ์ ํ๋(์กฐํ์ยท์ข์์)๋ ๋ฝยท์์ ์ฐ์ฐยทTTLยท๋ฉฑ๋ฑ์ผ๋ก ๋ฐฉ์ด
๐ฏ ๋ง๋ฌด๋ฆฌ
์ ์ด๋ํฐ์ธ๊ฐ?
โ ๊ตฌํ์ ์ฝ์ด ๋ฐ์ผ๋ก ๋ฐ์ด๋ด ์ฝ์ด ๊ท์น ๋ณด์กด
์ Controller๋ ์ด๋ํฐ์ธ๊ฐ?
โ ํฅ์ฌ๊ณ ๋ ์์ UI๋ Driving Adapter(Port In ํธ์ถ์)
๊ณ์ธต ์ญํ
- Domain โ ๋๋ฉ์ธ ๋ชจ๋ธ, ๊ท์น, ๋ถ๋ณ์
- Application โ ํฌํธ, Tx, ์ค์ผ์คํธ๋ ์ด์
- Adapters โ I/O ๊ตฌํ
๐ ๊ฐ๋
์ฑยท๋ณ๊ฒฝ ๋ด์ฑยท์ด์ ์์ ์ฑ์ ๋์์ ํ๋ณดํ ์ ์๋ค.
์ด๋ ๊ฒ ํ์ฌ ํ๋ก์ ํธ์์ ์ด ์ค๊ณ ๊ตฌ์กฐ๋ฅผ ํ ๋๋ก ๊ฐ๋ฐ์ ์งํํ๊ณ ์๋๋ฐ ์ค๊ณ ๊ตฌ์กฐ๋ ์ ๋ต์ด ์๊ธฐ์ ๊ณ์ํด์ ์ค๊ณ์ ๋ํด์ ๊ณ ๋ฏผํด๋ณด๋ ๊ณผ์ ์ ๊ฑฐ์น๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค!!
์ค์ค์ค ๊ธ์ด ๋๋ฌด ์ข๋ค. ๊ทผ๋ฐ ํ๋ ์๊ฐํ ๋ถ๋ถ์ด ์์ ๊ฒ ๊ฐ์.
"CacheProjectViewCountPort" ์ด๋ ๊ฒ ์บ์๋ผ๋ ๋งฅ๋ฝ์ด ์ธํฐํ์ด์ค๋ก ๋์จ๋ค๋ฉด ์ฌ์ฉํ๋ ๊ธฐ์ ์ด ์ธ๋ถ๋ก ๋ ธ์ถ๋๋๊ฑฐ ์๋๊น, "ModifyProjectViewCountPort"๋กํ์ง ์์ ์ด์ ๊ฐ ์์๊น?
์๋ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ๊ตฌํํ๋๋ผ๋ ์ด์ํ์ง ์์ ์ธํฐํ์ด์ค๋ผ๊ณ ์๊ฐ์ด ๋ค์ด์