1. 개발 이유

블로그를 만들어놓고 게시물을 쓰기 귀찮아졌다.. 그래서 어떻게 하면 꾸준히 쓸까 고민을 하다가 요즘 1일1백준을 하던게 생각났다. 백준과 연동하여 사용할 수 있는 solved.ac는 이렇게 하루에 문제를 계속 풀면 스트릭이 이어진다.(현재 51일 연속이다ㅎㅎ) solved.ac 스트릭 화면

내가 1일1백준을 하는 이유도 이 스트릭을 깨고싶지 않아서이다. 그래서 이 스트릭 개념을 가져와서 내 블로그에도 적용하기 위해 스트릭 기능을 구현했다.

<br><br>

2. 스트릭 기능 정의

github, solved.ac의 스트릭은 하루 단위이다. 하지만 게시물을 매일 작성해야 하는건 오히려 나의 의욕을 꺾을 것 같았다. 그래서 스트릭의 기준을 일주일 단위로 잡았다. 일주일에 한 편 정도 게시하면 퀄리티 있는 글을 작성할 수도 있고, 꾸준히 작성할 수 있을 것 같았기 때문이다.

그리고 추가로 게시 했다가 삭제하는 경우 스트릭을 어떻게 할지 고민해보았다. 스트릭은 게시물을 꾸준히 출간하도록 돕는 기록 장치이기 때문에 스트릭 기간이 지난 게시물을 삭제할땐 스트릭에 반영되지 않도록 정책을 정했다. 하지만 스트릭 기간에 출간했던 게시물을 삭제하는 경우 스트릭에 반영되도록 하여 게시물 작성을 장려하게 만들었다.

위의 정책과 필요성을 바탕으로 스트릭 기능을 정의하면 아래와 같다.

yeolpost 스트릭 기능

  • 단위: 일주일(월-일)
  • 업데이트 시간: 매주 월요일 오전 6:00UTC+9
  • 기능: 게시물을 작성하면 이번주 스트릭이 채워진다. N주 연속 작성 여부가 표시된다.

<br><br>

3. 스트릭 디자인

이 부분도 고민이 많았다. 내가 원하는 디자인은 상단에 보이면서도 기존 블로그의 디자인을 해치지 않는 미니멀한 디자인이었다. 하지만 난 디자이너가 아니기에 AI와 열심히 회의하며 지금의 스트릭 화면을 만들 수 있었다... 지금까지 디자인을 쭉 보여주며 지금 화면이 엄청난 고민의 결과라는걸 보여주고싶다.

1

정보량은 괜찮지만 디자인이 좋지 않다.


2

정보량도 많고 미니멀하지 않다.


3

이때부터 1년이 52주이므로 총 52개의 스트릭을 보여주려고 했다. 하지만 정보량도 너무 많고 무엇보다 예쁘지 않다.


4

이번에도 아직 1년 스트릭을 포기하지 못해 못생긴 타일모양으로 만들고 있었다.


5

...이때부터 1년을 모두 보여주자는 것을 포기했다. 최근 20주만 보여주어 연속성을 보여주기로 했다. 하지만 아직까지 기존의 블로그 다자인을 해친다.


6

조금 더 깔끔해졌다. 하지만 아직도 블로그와 어울리지 않아 이를 블로그 최하단에 배치하기로 했다. 그런데 문득 최하단에 있으면 동기부여가 일어나지 않을 것 같단 생각이 들었다.


7

최종 디자인이다. 정보를 최대한 제거하여 미니멀함만 남겼다. 그리고 블로그의 디자인을 해치치 않을정도로 크기를 줄여 동기부여와 타 사용자에게 꾸준히 작성했음을 간접적으로 드러내는 좋은 화면이 되었다.

물론 전문가가 보기엔 많이 부족하지만... 그래도 열심히 만들었다.

<br><br>

4. 스트릭 DB 스키마 및 엔티티

아래는 Entity 코드이다.

WeeklyStreak entity

package com.yeo_li.yeol_post.streak.domain;

import com.yeo_li.yeol_post.common.entity.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class WeeklyStreak extends BaseTimeEntity {

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

    @Column(nullable = false)
    private int year;

    @Column(nullable = false)
    private int weekNumber;

    @Column(nullable = false)
    private LocalDateTime startDateTime;

    @Column(nullable = false)
    private LocalDateTime endDateTime;

    @Builder.Default
    @Column(nullable = false)
    private int postCount = 0;

    public void addPostCount() {
        this.postCount++;
    }

    public void removePostCount() {
        this.postCount--;
    }
}

StreakStatus entity

package com.yeo_li.yeol_post.streak.domain;

import com.yeo_li.yeol_post.common.entity.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class StreakStatus extends BaseTimeEntity {

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

    @Column(nullable = false)
    private int year;

    @Column(nullable = false)
    private int weekNumber;

    private int currentStreakLength;

    private int maxStreakLength;
}

weekly_streakstreak_status 두 개로 테이블을 나누어 데이터를 저장하도록 했다. weekly_streak 테이블은 매주 기간과 기간 내에 작성한 게시물의 갯수를 저장하는 테이블이다. streak_status는 주차별 현재 연속 작성일과 최대 연속 작성일을 저장하는 테이블이다.

테이블을 두 개로 나눈 이유는 두 테이블의 성격이 다르기 때문이다.

우선 weekly_streak 테이블의 각 행의 정보는 독립적이다. 즉, 이번주 weekly_streak 데이터는 저번주 데이터의 영향을 받지 않는다는 것이다. 그리고 조회를 할 때 스트릭의 갯수가 20개이므로, 가장 최근에 생성된 weekly_streak 20개를 추출한다.

하지만 streak_status 테이블의 각 행의 정보는 weekly_streak과 이전 streak_status의 행에 영향을 받는다. 일종의 스냅샷의 성격을 띈다. 그리고 api에서는 현재 몇 주 연속 스트릭인지(currentStreakLength)만 반환하면 된다.

이렇게 weekly_streak의 데이터를 기반으로 streak_status를 생성할 수 있기 때문에 streak_status의 데이터가 꼬이게 되더라도 다시 복구할 수 있다.

만약 두 정보를 한 테이블에 담았다면 어느 속성들은 독립적이지만 다른 속성들은 과거 시간과 독립적이라 관리하는데 더 어려움을 겪었을 것 같다.

그리고 weekly_status는 20개씩 조회하는데, 그때마다 streak_status도 20개씩 가져오면 불필요한 데이터 조회라고 생각하여 분리했다.

<br><br>

아직 백엔드에 대해서 설명하지 못했지만 분량상 여기서 그만두고 2편으로 다시 써야겠다. 다음 글에선 DB Backfill과 스프링 스케쥴링 같은 백엔드 기술과 관련되어 기술할 예정이다.