๐Ÿฐ [flowbit] #4. Project ๋„๋ฉ”์ธ ๋„์ž… - ํ๋ฆ„์˜ ๋‹จ์œ„๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค

bean8080๐Ÿซ›ยท2026๋…„ 4์›” 29์ผ

flowbit ๐Ÿฐโ˜˜๏ธ

๋ชฉ๋ก ๋ณด๊ธฐ
4/15
post-thumbnail

โ˜˜๏ธ 1. ์˜ค๋Š˜ ๋ชฉํ‘œ

์–ด์ œ๋Š”
TaskEvent๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ
์ž‘์—…์˜ ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ธฐ๋กํ•˜๊ณ ,

๊ทธ๊ฑธ ์กฐํšŒํ•ด์„œ
ํ๋ฆ„์„ ๋ณด์—ฌ์ฃผ๋Š” ๋‹จ๊ณ„๊นŒ์ง€ ์™”๋‹ค.

๊ทผ๋ฐ ์—ฌ๊ธฐ์„œ ๋”ฑ ๊ฑธ๋ฆฐ ๊ฒŒ ์žˆ์—ˆ๋‹ค.

โ€ญโ†’ ์ž‘์—…์€ ํ˜ผ์ž ์กด์žฌํ•˜๋Š”๊ฐ€?

ํ˜„์‹ค์—์„œ๋Š” ๊ทธ๋ ‡์ง€ ์•Š๋‹ค.

์ž‘์—…์€ ํ•ญ์ƒ
์–ด๋–ค ๋‹จ์œ„ ์•ˆ์—์„œ ์›€์ง์ธ๋‹ค.

๊ทธ๋ž˜์„œ ์˜ค๋Š˜ ๋ชฉํ‘œ๋Š” ์ด๊ฑฐ์˜€๋‹ค.

โ€ญโ†’ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ํ๋ฆ„ ๋‹จ์œ„ ์•ˆ์— ๋„ฃ์ž

์˜ˆ๋ฅผ ๋“ค์–ด,

์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์ง„ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด
๊ฐ ์ž‘์—…์˜ ํ๋ฆ„์€ ๋ณด์ด๋Š”๋ฐ

โ†’ ์ด ์ž‘์—…๋“ค์ด ์™œ ๊ฐ™์ด ์กด์žฌํ•˜๋Š”์ง€
โ†’ ๊ฐ™์ด ์–ด๋–ค ๋ฐฉํ–ฅ์œผ๋กœ ํ˜๋Ÿฌ๊ฐ€๋Š”์ง€

๋Š” ํ•œ ๋ฒˆ์— ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.


โ˜˜๏ธ 2. Task๋งŒ ๋ณด๋ฉด ํ๋ฆ„์ด ์•„๋‹ˆ๋ผ "์ "์ด๋‹ค

์ง€๊ธˆ๊นŒ์ง€ ๊ตฌ์กฐ๋Š” ์ด๋žฌ๋‹ค.

Task โ†’ ํ˜„์žฌ ์ƒํƒœ
TaskEvent โ†’ ์ƒํƒœ ๋ณ€ํ™”

์ด๊ฑธ๋กœ๋„ ํ๋ฆ„์€ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด ์ž‘์—…์ด ์™œ ์กด์žฌํ•˜๋Š”์ง€๋Š” ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค

์ž‘์—… ํ•˜๋‚˜๋งŒ ๋ณด๋ฉด
ํ๋ฆ„์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ƒฅ ์ ์ด๋‹ค.

์ฆ‰,

๊ฐœ๋ณ„ ์ž‘์—…์˜ ๋ณ€ํ™”๋Š” ๋ณด์ด์ง€๋งŒ
์ „์ฒด ๋งฅ๋ฝ์€ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค

๊ทธ๋ž˜์„œ ํ•„์š”ํ•œ ๊ฒŒ

โ€ญโ†’ Project


โ˜˜๏ธ 3. Project ๋„๋ฉ”์ธ ์ถ”๊ฐ€

Task ์œ„์— Project๋ฅผ ํ•˜๋‚˜ ์–น์—ˆ๋‹ค.

โ†’ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ํ๋ฆ„์œผ๋กœ ๋ฌถ๊ธฐ ์œ„ํ•œ ๋‹จ์œ„๋‹ค

๊ตฌ์กฐ๋Š” ์ด๋ ‡๊ฒŒ ๋ฐ”๋€๋‹ค.

Project โ†’ ํ๋ฆ„ ๋‹จ์œ„
Task โ†’ ๊ทธ ์•ˆ์˜ ์ž‘์—…
TaskEvent โ†’ ์ž‘์—… ๋ณ€ํ™”

์ด์ œ๋Š”

โ€ญโ†’ ์ž‘์—… ํ•˜๋‚˜๊ฐ€ ์•„๋‹ˆ๋ผ
โ€ญโ†’ ํ”„๋กœ์ ํŠธ ์•ˆ์—์„œ ์›€์ง์ด๋Š” ์ž‘์—…๋“ค์ด ๋œ๋‹ค

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String description;

@Enumerated(EnumType.STRING)
private ProjectStatus status;

private LocalDateTime createdAt;
private LocalDateTime startedAt;
private LocalDateTime completedAt;
private LocalDateTime deletedAt;

์ฒ˜์Œ์ด๋ผ ํ•„๋“œ๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ์žก์•˜๋‹ค.

id
name
description
status
createdAt
startedAt
completedAt
deletedAt

Project๋„ ์ƒ์„ฑ, ์‹œ์ž‘, ์™„๋ฃŒ, ์‚ญ์ œ ์‹œ์ ์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.
๋‚˜์ค‘์— ํ”„๋กœ์ ํŠธ ๋‹จ์œ„ ํƒ€์ž„๋ผ์ธ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‹œ๊ฐ„ ํ•„๋“œ๋ฅผ ๋ฏธ๋ฆฌ ์—ด์–ด๋‘” ์…ˆ์ด๋‹ค.


โ˜˜๏ธ 4. Task๋Š” ๋ฌด์กฐ๊ฑด Project์— ์†ํ•˜๊ฒŒ ํ–ˆ๋‹ค

์—ฌ๊ธฐ์„œ ๊ณ ๋ฏผ์ด ํ•˜๋‚˜ ์žˆ์—ˆ๋‹ค.

โ€ญโ†’ projectId๊ฐ€ null์ธ Task๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

๊ฒฐ๋ก ์€ NO

Flowbit์€ ์• ์ดˆ์—
ํ๋ฆ„์„ ๋ณด๋Š” ์‹œ์Šคํ…œ์ด๋ผ์„œ

โ€ญโ†’ Task๋Š” ๋ฐ˜๋“œ์‹œ ์–ด๋–ค ํ๋ฆ„ ์•ˆ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค

๊ทธ๋ž˜์„œ ๊ตฌ์กฐ๋ฅผ ์ด๋ ‡๊ฒŒ ๊ณ ์ •ํ–ˆ๋‹ค.

โ€ญโ†’ Task๋Š” ๋ฐ˜๋“œ์‹œ Project์— ์†ํ•œ๋‹ค


โ˜˜๏ธ 5. ๋Œ€์‹  Default Project๋ฅผ ๋„ฃ์—ˆ๋‹ค โญ

๊ทผ๋ฐ ๋งค๋ฒˆ projectId ๋„ฃ๋Š” ๊ฑด ๊ท€์ฐฎ๋‹ค.

๊ทธ๋ž˜์„œ ํƒ€ํ˜‘ํ•œ ๊ฒŒ ์ด๊ฑฐ๋‹ค.

Default Project

๋™์ž‘์€ ๊ฐ„๋‹จํ•˜๋‹ค.

projectId ์žˆ์œผ๋ฉด โ†’ ๊ทธ ํ”„๋กœ์ ํŠธ ์‚ฌ์šฉ
์—†์œผ๋ฉด โ†’ Default Project ์ž๋™ ์—ฐ๊ฒฐ

Optional<Project> project = projectRepository.findByName("DEFAULT");

if (project.isPresent()) {
    return project.get();
}

Project defaultProject = new Project(
        "DEFAULT",
        "๊ธฐ๋ณธ ํ”„๋กœ์ ํŠธ",
        ProjectStatus.READY,
        LocalDateTime.now()
);

return projectRepository.save(defaultProject);

์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” Optional์˜ orElseGet์„ ์‚ฌ์šฉํ•ด์„œ,
DEFAULT ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์„ ๋•Œ๋งŒ ์ƒˆ๋กœ ์ƒ์„ฑ๋˜๋„๋ก ํ–ˆ๋‹ค.

์ด ๊ตฌ์กฐ์˜ ์žฅ์ ์€ ๋ช…ํ™•ํ•˜๋‹ค.

โ†’ ๊ตฌ์กฐ๋Š” ๊นจ์ง€์ง€ ์•Š๋Š”๋‹ค
โ†’ Task๋Š” ํ•ญ์ƒ ํ๋ฆ„ ์•ˆ์— ์กด์žฌํ•œ๋‹ค


โ˜˜๏ธ 6. Task ์ƒ์„ฑ ๋กœ์ง์ด ๋ฐ”๋€Œ์—ˆ๋‹ค

๊ธฐ์กด์—๋Š” ๊ทธ๋ƒฅ

request.getProjectId()

์ด๊ฑธ ๊ทธ๋Œ€๋กœ ์ผ๋Š”๋ฐ

์ด์ œ๋Š” ์ƒ์„ฑ ์‹œ์ ์— ํŒ๋‹จํ•œ๋‹ค.

null์ด๋ฉด โ†’ default ํ”„๋กœ์ ํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
์žˆ์œผ๋ฉด โ†’ ์กด์žฌํ•˜๋Š”์ง€ ๊ฒ€์ฆ

Long projectId;

if (request.getProjectId() == null) {
    Project defaultProject = projectService.getOrCreateDefaultProject();
    projectId = defaultProject.getId();
} else {
    projectId = projectRepository.findById(request.getProjectId())
            .orElseThrow(() -> new IllegalArgumentException("ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."))
            .getId();
}

์ด๊ฑธ ๋„ฃ๊ณ  ๋‚˜๋‹ˆ๊นŒ

โ†’ Task๊ฐ€ ์ ˆ๋Œ€ ๊ณ ๋ฆฝ๋˜์ง€ ์•Š๋Š”๋‹ค

๊ทธ๋ฆฌ๊ณ  ๋™์‹œ์—

โ†’ ์‚ฌ์šฉ์„ฑ์€ ์œ ์ง€ํ•˜๋ฉด์„œ
โ†’ ๊ตฌ์กฐ๋ฅผ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๊ฐ€ ๋๋‹ค


โ˜˜๏ธ 7. Project API๋„ ์ถ”๊ฐ€

Project๋„ ์™ธ๋ถ€์—์„œ ์“ธ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด์„œ
API๋„ ๊ฐ™์ด ๋งŒ๋“ค์—ˆ๋‹ค.

POST /projects
GET /projects
GET /projects/{id}

Project ๋‹จ๊ฑด ์กฐํšŒ ๊ฒฐ๊ณผ

ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋‹จ๊ฑด ์กฐํšŒ๊นŒ์ง€ ํ™•์ธํ–ˆ๋‹ค.

์ด์ œ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ โ†’ Task ์—ฐ๊ฒฐ ํ๋ฆ„์ด ์™„์„ฑ๋๋‹ค.


โ˜˜๏ธ 8. ๊ตฌ์กฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ์—ˆ๋ƒ

์ด์ „:

  • Task ์ค‘์‹ฌ
  • ์ž‘์—… ๋‹จ์œ„ ํ๋ฆ„

์ง€๊ธˆ:

  • Project ์ค‘์‹ฌ
  • ํ”„๋กœ์ ํŠธ ๋‹จ์œ„ ํ๋ฆ„

์ฐจ์ด๋Š” ๋ช…ํ™•ํ•˜๋‹ค.

ํ•  ์ผ ๊ด€๋ฆฌ์—์„œ
ํ๋ฆ„์„ ๋‹ค๋ฃจ๋Š” ๊ตฌ์กฐ๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค


โ˜˜๏ธ 9. ์˜ค๋Š˜ ์ •๋ฆฌ

์˜ค๋Š˜ ํ•œ ๊ฑธ ํ•œ ์ค„๋กœ ์ •๋ฆฌํ•˜๋ฉด ์ด๊ฑฐ๋‹ค.

ํฉ์–ด์ ธ ์žˆ๋˜ ์ž‘์—…๋“ค์„
ํ”„๋กœ์ ํŠธ๋ผ๋Š” ํ•˜๋‚˜์˜ ํ๋ฆ„ ๋‹จ์œ„๋กœ ๋ฌถ์—ˆ๋‹ค


โ˜˜๏ธ 10. ํ˜„์žฌ ์ƒํƒœ

Project ๋„๋ฉ”์ธ โœ”๏ธ
Task โ†” Project ์—ฐ๊ฒฐ โœ”๏ธ
Default Project โœ”๏ธ
์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ โœ”๏ธ
ํƒ€์ž„๋ผ์ธ โœ”๏ธ

์ด์ œ๋Š” ์ง„์งœ ํ๋ฆ„์„ ๋‹ค๋ฃจ๋Š” ๊ตฌ์กฐ๊ฐ€ ๋๋‹ค


โ˜˜๏ธ 11. ๋‹ค์Œ ๋ชฉํ‘œ

๋‹ค์Œ์€ ์ด๊ฑฐ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋‹จ์œ„ ํƒ€์ž„๋ผ์ธ
ํ”„๋กœ์ ํŠธ ํ๋ฆ„ ๋ถ„์„

์ด์ œ ํ•œ ๋‹จ๊ณ„ ๋” ๊ฐ€๋ฉด

์ž‘์—… ๋‹จ์œ„ ํ๋ฆ„์—์„œ
ํ”„๋กœ์ ํŠธ ๋‹จ์œ„ ํ๋ฆ„์œผ๋กœ ํ™•์žฅ๋œ๋‹ค

0๊ฐœ์˜ ๋Œ“๊ธ€