[๐Ÿ”ฅTroubleShooting - MoodBuddy๐Ÿ”ฅ] ๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ธฐ๊ธฐ๋กœ ์ผ๊ธฐ์— ์ ‘๊ทผํ•œ๋‹ค๋ฉด..?

._mungยท2025๋…„ 3์›” 9์ผ
0

MoodBuddy

๋ชฉ๋ก ๋ณด๊ธฐ
2/8

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

ํŒ€์› : PM(1) / Design(1) / Frontend(2) / Backend(3)
๊ธฐ๊ฐ„ : 2024.03 ~ 2025.03
๋งํฌ : https://github.com/M-ung/MoodBuddy_Server
์„œ๋น„์Šค ๋‚ด์šฉ : ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์ผ๊ธฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฐ์ • ๋ถ„์„ํ•˜๋Š” ์›น ์„œ๋น„์Šค
์†Œํ†ต : GitHub, Slack, Notion, Discord


๐Ÿ”ฅTroubleShooting๐Ÿ”ฅ

Problems

์šฐ๋ฆฌ ์„œ๋น„์Šค "MoodBuddy" ๋Š” ์‚ฌ์šฉ์ž ํ˜ผ์ž๋งŒ์˜ ๊ณต๊ฐ„์ด๊ณ  ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๊ณต๊ฐ„์ด๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ์„ ํฌ๊ฒŒ ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋งŒ์•ฝ ์ผ๊ธฐ ์ˆ˜์ •, ์ผ๊ธฐ ์‚ญ์ œ, ๋ถ๋งˆํฌ, ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜์˜ ์ผ๊ธฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋™์‹œ์— ํ˜ธ์ถœํ•  ์ผ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ๋™์‹œ์„ฑ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋Šฅ ๊ตฌํ˜„์— ๋ชฐ๋‘ํ–ˆ๋‹ค.

ํ•˜.์ง€.๋งŒ ๋งŒ์•ฝ์— '๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ธฐ๊ธฐ๋กœ ์ผ๊ธฐ์— ์ ‘๊ทผํ•œ๋‹ค๋ฉด..?' ๋ผ๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋…ธํŠธ๋ถ, ํ•ธ๋“œํฐ์œผ๋กœ ๋™์‹œ์— ์ผ๊ธฐ ์ˆ˜์ • ๋˜๋Š” ์‚ญ์ œ๋ฅผ ํ•œ๋‹ค๋ฉด ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์œ„ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ์ •๋ง ์ •๋ง ์ •๋ง ์ ์€ ์ผ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๊ทธ๋ž˜๋„ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ์œ„ ๊ฐ™์€ ๊ฒฝ์šฐ๋„ ์ƒ๊ฐํ•ด์„œ ๊ฐœ๋ฐœ์„ ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.


How

์œ„์—์„œ ๋งํ–ˆ๋“ฏ์ด ์œ„ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ์ž์ฃผ ์ผ์–ด๋‚  ์ผ์ด ์•„๋‹ˆ๋ผ๊ณ  ํŒ๋‹จํ•˜์˜€๊ธฐ์— ์ตœ๋Œ€ํ•œ ๊ฐ„๋‹จํ•˜๊ณ  ๊ฐ€๋ณ๊ฒŒ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์„ ํƒํ•œ ๋ฐฉ์•ˆ์€ "๋‚™๊ด€์  ๋ฝ"์ด๋‹ค.

๊ทธ ์ด์œ ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๋‚™๊ด€์  ๋ฝ์€ ๋™์‹œ ์ˆ˜์ •์ด ์ ์€ ๊ฒฝ์šฐ ํšจ์œจ์ ์ด๋‹ค.
ํŠธ๋žœ์žญ์…˜ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ๋•Œ๋งŒ ๊ฐ์ง€ํ•˜์—ฌ ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์ ๋‹ค.
๋น„๊ด€์  ๋ฝ๋ณด๋‹ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฝ์„ ์ตœ์†Œํ™”ํ•˜์—ฌ ๋ถ€ํ•˜๋ฅผ ์ค„์ธ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์ ํ•ฉํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.


Process

๋จผ์ € ์ผ๊ธฐ(Diary) ์—”ํ‹ฐํ‹ฐ๋ฐ version ์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์—ˆ๋‹ค.

๐Ÿ“ Diary.java

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "diary")
public class Diary extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "date", nullable = false)
    private LocalDate date;

    @Column(name = "content", nullable = false, columnDefinition = "text")
    private String content;

    @Enumerated(EnumType.STRING)
    @Column(name = "weather", nullable = false)
    private DiaryWeather weather;

    @Enumerated(EnumType.STRING)
    @Column(name = "emotion")
    private DiaryEmotion emotion;

    @Enumerated(EnumType.STRING)
    @Column(name = "subject")
    private DiarySubject subject;

    @Column(name = "summary", columnDefinition = "varchar(255)")
    private String summary;

    @Column(name = "user_id", nullable = false, columnDefinition = "bigint")
    private Long userId;

    @Column(name = "book_mark")
    private Boolean bookMark;

    @Enumerated(EnumType.STRING)
    @Column(name = "font")
    private DiaryFont font;

    @Enumerated(EnumType.STRING)
    @Column(name = "font_size")
    private DiaryFontSize fontSize;

    @Column(name = "thumbnail", columnDefinition = "text")
    private String thumbnail;

    @Enumerated(EnumType.STRING)
    @Column(name = "mood_buddy_status")
    private MoodBuddyStatus moodBuddyStatus;

    @Version
    @Column(name = "version", nullable = false)
    private Long version;
 }

๋‹ค์Œ์œผ๋กœ ์ผ๊ธฐ ์ˆ˜์ •๊ณผ ์ผ๊ธฐ ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ์•„๋ž˜์™€ ๊ฐ™์ด version ์ถฉ๋Œ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•ด ์ฃผ์—ˆ๋‹ค.

๐Ÿ“ DiaryServiceImpl.java

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class DiaryServiceImpl implements DiaryService {
    private final DiaryRepository diaryRepository;

    @Override
    @Transactional
    public Long updateDiary(final Long userId,  DiaryReqUpdateDTO requestDTO) {
        try {
            var findDiary = findDiaryById(userId, requestDTO.diaryId());
            findDiary.updateDiary(requestDTO);
            return findDiary.getId();
        } catch (ObjectOptimisticLockingFailureException ex) {
            throw new DiaryConcurrentUpdateException(ErrorCode.DIARY_CONCURRENT_UPDATE);
        }
    }

    @Override
    @Transactional
    public LocalDate deleteDiary(final Long userId,  final Long diaryId) {
        try {
            final var findDiary = findDiaryById(userId, diaryId);
            findDiary.updateMoodBuddyStatus(MoodBuddyStatus.DIS_ACTIVE);
            return findDiary.getDate();
        } catch (OptimisticLockException ex) {
            throw new DiaryConcurrentUpdateException(ErrorCode.DIARY_CONCURRENT_DELETE);
        }
    }
}

๐Ÿ“ DiaryRepository.java

public interface DiaryRepository extends JpaRepository<Diary, Long>, DiaryRepositoryCustom {
    boolean existsByUserIdAndDate(Long userId, LocalDate date);

    @Lock(LockModeType.OPTIMISTIC)
    Optional<Diary> findByUserIdAndIdAndMoodBuddyStatus(final Long userId, final Long diaryId, MoodBuddyStatus moodBuddyStatus);
}

๋˜ ์œ„ ์ผ๊ธฐ ์ˆ˜์ •๊ณผ ์ผ๊ธฐ ์‚ญ์ œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ถ”๊ฐ€์ ์œผ๋กœ ๋“  ๊ณ ๋ฏผ์ด "์ผ๊ธฐ ์ €์žฅ" ๋˜ํ•œ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ์šฐ๋ฆฌ "MoodBuddy" ์„œ๋น„์Šค๋Š” ํ•˜๋ฃจ์— ํ•œ ๋ฒˆ ์ผ๊ธฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•˜๋‚˜์˜ ๋‚ ์งœ์— ์ผ๊ธฐ๊ฐ€ 2๋ฒˆ ์ด์ƒ ์ž‘์„ฑ๋˜๋ฉด ์•ˆ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ด ๋ฌธ์ œ๋Š” ํ…Œ์ด๋ธ”์— ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ฑธ์–ด์„œ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ–ˆ๋‹ค.

@Table(name = "diary", 
uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "date"}))

ํ…Œ์ŠคํŠธ ๋ชจ๋‘ ํ†ต๊ณผํ•˜๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


Result

๋ฌธ์ œ ํ•ด๊ฒฐ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์—ˆ๋‹ค.
1. ํฌ๋ฐ•ํ•œ ๋™์‹œ์„ฑ ์ถฉ๋Œ ์ƒํ™ฉ์„ ๊ณ ๋ คํ•ด ๋‚™๊ด€์  ๋ฝ์œผ๋กœ ์ฒ˜๋ฆฌ.
2. 10๋Œ€ ๊ธฐ๊ธฐ ๊ธฐ์ค€, ๋™์‹œ์— ์š”์ฒญ ์ค‘ 1๋Œ€ ์„ฑ๊ณต, 9๋Œ€ ์‹คํŒจ.


Thoughts

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋Š” ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๊ณณ์—์„œ๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๊นจ๋‹ฌ์•˜๋‹ค. ์ฒ˜์Œ์—๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ™์€ ์ผ๊ธฐ๋ฅผ ์—ฌ๋Ÿฌ ๊ธฐ๊ธฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋™์‹œ์— ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ์ผ์ด ๊ทนํžˆ ๋“œ๋ฌผ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์ง€๋งŒ, ๊ฐœ๋ฐœ์ž๋กœ์„œ ๊ทนํžˆ ๋“œ๋ฌธ ๊ฒฝ์šฐ๋ผ๋„ ๋Œ€๋น„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€ ์„œ๋น„์Šค์˜ ๊ธฐ๋ณธ์ด๋ผ๋Š” ์ ์„ ๋Š๋ผ๊ณ  ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํŠนํžˆ ๋‚™๊ด€์  ๋ฝ์„ ์ ์šฉํ•˜๋ฉด์„œ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ๋‹จ์ˆœํ•˜๊ณ  ๊ฐ€๋ณ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , JPA์˜ @Version ๊ธฐ๋Šฅ๊ณผ @UniqueConstraint ๋ฅผ ํ™œ์šฉํ•ด ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฝ์„ ์ตœ์†Œํ™”ํ•˜๋ฉด์„œ๋„ ์•ˆ์ •์ ์ธ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ์—ฌ๋Ÿฌ ๋™์‹œ์„ฑ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ๊ฒŒ ๋˜์—ˆ์ง€๋งŒ, ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์„œ๋น„์Šค์˜ ํŠน์„ฑ์— ๋งž์ถฐ ์‹ ์ค‘ํ•˜๊ฒŒ ์„ ํƒํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ๋„ ๊นจ๋‹ฌ์•˜๋‹ค.
๋งŒ์•ฝ ์ผ๊ธฐ์˜ ๋™์‹œ ์ˆ˜์ •๊ณผ ์‚ญ์ œ๊ฐ€ ์žฆ๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์™€ ์ผ๊ธฐ๋ฅผ ๊ณต์œ ํ•˜๋Š” ์„œ๋น„์Šค์˜€๋‹ค๋ฉด ๋‚™๊ด€์  ๋ฝ๋ณด๋‹ค๋Š” ๋น„๊ด€์  ๋ฝ์ด๋‚˜ ๋ถ„์‚ฐ ๋ฝ์„ ๊ณ ๋ คํ–ˆ์„ ๊ฒƒ์ด๋‹ค.

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์„œ๋น„์Šค์˜ ํŠน์ง•์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋™์‹œ์„ฑ ์ œ์–ด ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋ฉฐ, ์„ฑ๋Šฅ๊ณผ ์•ˆ์ •์„ฑ์„ ๋ชจ๋‘ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ๋ชธ์†Œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๋‹ค.


profile
๐Ÿ’ป ๐Ÿ’ป ๐Ÿ’ป

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

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด