실제 운영 환경에서는 ddl-auto
설정을 validate
또는 none
으로 사용하는 것이 일반적입니다. validate
는 Hibernate가 엔티티와 데이터베이스 스키마 간의 일관성을 검증하지만, 스키마를 변경하지 않습니다. 반면, 현재 개발 환경에서는 update
로 설정하여 엔티티의 변경 사항을 자동으로 스키마에 반영하고 있습니다.
하지만 이 update
방식에는 치명적인 단점이 있습니다. 엔티티에 새로운 필드를 추가하면 해당 필드가 자동으로 스키마에 반영되지만, 기존 데이터는 이 필드에 값이 없으므로 NULL 값이 들어가게 됩니다. 이로 인해 데이터 무결성 문제가 발생할 수 있습니다. 또한, 외래 키나 제약 조건이 변경될 경우 참조 무결성이 깨져 운영 환경에서 심각한 데이터 손실 및 오류를 초래할 위험이 있습니다.
이러한 문제는 운영 환경에서 더욱 심각해집니다. 로컬 개발 환경이나 테스트 서버에서는 ddl-auto
설정을 create
, create-drop
, update
로 설정하여 엔티티 구조 변경 시 스키마를 자동으로 반영할 수 있지만, 프로덕션 환경에서는 이러한 설정을 사용할 수 없습니다. 프로덕션 서버에서 테이블을 DROP하는 것은 실제 사용자 데이터가 손실될 수 있기 때문에 불가능합니다.
기존 테이블에 새로운 컬럼을 추가하거나 수정할 때는 ALTER TABLE
명령어를 사용합니다. 또한, 새로운 필드에 초기값을 설정해야 하는 경우 데이터 마이그레이션 스크립트를 작성하여 기존 데이터를 새로운 스키마에 맞게 변환해야 합니다.
ALTER TABLE member ADD COLUMN cellphone VARCHAR(255) NOT NULL;
Flyway
와 같은 마이그레이션 도구를 활용해 이러한 문제를 해결하는 것이 효과적 입니다.Flyway
는 데이터베이스 스키마 버전 관리 및 마이그레이션을 자동화하는 도구로, 주로 SQL 스크립트나 Java 코드로 작성된 스키마 변경을 관리하고 적용하는 데 사용됩니다. Flyway의 동작 원리는 다음과 같은 절차로 이루어집니다.flyway_schema_history
테이블을 찾습니다.(비어있는 데이터베이스에 Flyway가 연결된 후 flyway_schema_history 테이블을 생성한 모습입니다.)
/resources/db/migration
디렉토리에서 SQL 또는 Java로 작성된 마이그레이션 스크립트를 스캔합니다.Flyway는 작은 버전 번호부터 큰 버전 번호 순서대로 마이그레이션을 실행합니다. 예를 들어, 마이그레이션 파일이
V1__init.sql
, V2__add_table.sql
, V3__add_column.sql
형태로 존재한다면 Flyway는 이 순서대로 파일을 실행합니다.
각 마이그레이션이 성공적으로 완료되면 해당 내역이 flyway_schema_history
테이블에 기록됩니다.
Flyway는 3가지 종류의 마이그레이션을 제공합니다:
Versioned Migrations
Flyway의 핵심 기능입니다. 마이그레이션 스크립트는 버전 번호에 따라 실행되며, 데이터베이스의 현재 스키마 버전과 비교하여 차이점이 있을 경우 순차적으로 마이그레이션을 수행합니다.
예를 들어, 현재 데이터베이스가 버전 5이고 마이그레이션 파일이 버전 9까지 있다면, Flyway는 6~9 버전의 스크립트를 순차적으로 실행합니다.
Undo Migrations (유료 기능)
Repeatable Migrations
예를 들어 member 테이블에 대한 JPA 엔티티는 아래와 같이 구성됩니다.
@Table(name = "member")
@Entity
public class Member extends BaseTime {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "email", nullable = false)
private String email;
}
Spring Boot는 기본적으로 Flyway 같은 고수준의 데이터베이스 마이그레이션 도구를 지원합니다. 이러한 도구들은 데이터베이스 스키마를 효과적으로 관리하고 자동으로 초기화할 수 있게 도와줍니다. (참고)
저는 현재 서비스 환경에서 Docker를 사용하고 있지만, 반드시 Docker를 사용할 필요는 없습니다. Docker를 통해 MySQL 컨테이너를 띄우려면 아래와 같은 명령어를 사용할 수 있습니다.
docker run --name test -e MYSQL_ROOT_PASSWORD=1234 -d mysql:8.0.32
위 명령어로 MySQL 8.0.32 버전을 실행하면 컨테이너가 생성되고, 다음과 같은 상태를 확인할 수 있습니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
57c250407728 mysql:8.0.32 "docker-entrypoint.s…" 5 weeks ago Up 5 weeks 0.0.0.0:3306->3306/tcp test
Flyway를 프로젝트에 적용하기 위해 build.gradle
파일에 Flyway와 MySQL 의존성을 추가합니다.
dependencies {
// flyway
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql' // MySQL 8.X 버전이거나, MariaDB를 사용하는 경우
}
Flyway를 제대로 사용하려면 Spring Boot의 application-{}.yml
파일에 다음과 같이 설정을 추가해야 합니다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://{Private IP}:3306/test
username: root
password: 1234
jpa:
# ...
hibernate:
ddl-auto: validate # Hibernate가 스키마 검증만 수행
defer-datasource-initialization: false # Flyway 마이그레이션 후 JPA 초기화
flyway:
enabled: true # Flyway 활성화
baseline-on-migrate: true # 기존 스키마가 있을 때 기준을 설정
locations: classpath:db/migration # 마이그레이션 파일 경로
기존 데이터베이스에 Flyway를 도입하는 경우 baseline-on-migrate: true
옵션이 중요합니다. 이 옵션은 Flyway가 마이그레이션 기록을 관리하는 테이블(flyway_schema_history
)을 처음으로 생성할 때 사용됩니다.
저의 경우, 프로젝트를 진행하는 중간에 flyway
를 도입하기 때문에 해당 옵션을 true로 설정했습니다.
마이그레이션 스크립트는 resources/db/migration 디렉토리에 저장되며, 파일명은 V1__init.sql
처럼 버전과 설명을 포함합니다. 첫 번째 마이그레이션 파일을 다음과 같이 작성합니다.
파일 경로는 resources/db/migration
이며 파일명은 V1__init.sql
로 생성해주세요.
-- member 테이블 생성
CREATE TABLE IF NOT EXISTS member (
id BIGINT AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL,
updated_at DATETIME(6) NOT NULL,
PRIMARY KEY (id)
);
Flyway는 스크립트를 순차적으로 실행하므로, 마이그레이션 파일명은 버전을 포함해야 하며, 파일 이름에서 언더스코어(_)를 두 개 사용하는 규칙을 지켜야 합니다.
애플리케이션을 실행하면 Flyway
가 정상적으로 마이그레이션 스크립트를 실행하고, 스키마 변경이 적용됩니다.
로그에서 Flyway의 실행 상태를 확인할 수 있습니다.
INFO org.flywaydb.core.internal.command.DbMigrate - Current version of schema test: 1
INFO org.flywaydb.core.internal.command.DbMigrate - Schema test is up to date. No migration necessary.
Flyway의 마이그레이션 스크립트는 버전 순서대로 실행됩니다. 숫자가 작은 파일부터 순서대로 실행되며, 숫자가 동일한 경우 파일명이 알파벳순으로 실행됩니다. 버전 관리가 중요하므로 명명 규칙을 지키는 것이 중요합니다.
버전은 정수로 처리되므로 V3.10과 V3.2 중 V3.2가 먼저 실행될 수 있음을 주의해야 합니다.
Flyway를 사용할 때는 Spring Boot의 schema.sql
과 data.sql
을 사용해 데이터베이스를 초기화하지 않아야 합니다. Flyway가 스키마를 관리하므로, 수동으로 스키마를 정의하는 것은 오류를 일으킬 수 있습니다.
또한, 새로운 엔티티가 추가될 때마다 마이그레이션 스크립트를 작성하는 습관을 들이는 것이 중요합니다. 일반적으로 프로덕션 환경에서는 ddl-auto
를 validate
로 설정하여 JPA가 자동으로 스키마를 수정하지 못하도록 합니다.
(참고)
기존 스키마에 Flyway를 도입할 때 flyway_schema_history 테이블이 없으면 오류가 발생할 수 있습니다.
에러 내용
Error creating bean with name 'flywayInitializer': Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Found non-empty schema(s) test but no schema history table.
이 에러는 Flyway가 데이터베이스에 접속했을 때, 이미 존재하는 비어 있지 않은 스키마를 발견했지만, Flyway의 스키마 히스토리 테이블(flyway_schema_history
)이 없어서 발생한 문제입니다. Flyway는 마이그레이션 이력을 추적하기 위해 이 히스토리 테이블을 사용합니다. 따라서 이미 데이터가 있는 기존 데이터베이스에 Flyway를 처음 적용하려고 할 때 이러한 문제가 발생할 수 있습니다.
해결방안
기존 데이터베이스에 Flyway를 적용할 때, Flyway가 기존의 데이터베이스 스키마를 무시하고 히스토리 테이블을 생성하도록 해야 합니다. 이 작업을 위해 Flyway의 baselineOnMigrate: true
설정을 사용하면 됩니다.
이 설정을 추가하면, Flyway는 기존에 비어 있지 않은 데이터베이스에서 첫 마이그레이션을 실행할 때 히스토리 테이블을 생성하고 이를 기준으로 마이그레이션을 진행합니다.
application.{dev/local}.yml에 추가
spring:
flyway:
enabled: true
baselineOnMigrate: true # 이 옵션은 기존 데이터베이스가 있고, 그 위에 처음으로 Flyway를 적용할 때 사용됨
locations: classpath:db/migration
새로운 필드를 엔티티에 추가했지만, 이를 반영한 마이그레이션 스크립트를 작성하지 않으면 JPA에서 에러가 발생할 수 있습니다.
Member 엔티티 클래스 - cellphone 컬럼 추가
@Table(name = "member")
@Entity
public class Member extends BaseTime {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "cellphone")
private String cellphone; // 추가된 필드
}
에러 내용은 아래와 같습니다.
Schema-validation: missing column [cellphone] in table [member]
이 문제를 해결하기 위해 새로운 마이그레이션 스크립트를 생성해야 합니다. 예를 들어 V2__member_add_cellphone.sql
파일을 생성하고, 다음과 같이 컬럼을 추가합니다.
예를 들어, V2__member_add_cellphone.sql
파일을 생성하고, 다음과 같이 컬럼을 추가합니다.
ALTER TABLE member ADD COLUMN cellphone VARCHAR(255);
이 스크립트는 기존 테이블에 새로운 cellphone 컬럼을 추가합니다. 파일을 적용하고 애플리케이션을 다시 실행하면, 스키마 버전이 업데이트됩니다.
마이그레이션 적용 로그 예시
해당 파일을 적용한 뒤, 애플리케이션을 다시 실행하면 아래와 같이 Version이 1 에서 2로 바뀐 것을 아래 로그를 통해 확인할 수 있습니다.
Migrating schema `{데이터베이스 내의 스키마 이름}` to version "2 - member add cellphone"
위와 같이 데이터베이스를 확인해보면, 새로운 테이블이 하나 생성된 것을 확인할 수 있습니다. flyway_schema_history
라는 테이블을 통해 Flyway는 마이그레이션에 대한 버전 관리를 합니다.
이미 적용된 마이그레이션 파일을 수정하는 것은 매우 위험할 수 있습니다. Flyway는 각 마이그레이션 파일의 체크섬(무결성)을 관리하므로, 파일을 수정하면 아래와 같은 오류가 발생할 수 있습니다.
예시:
V1__init.sql
파일에 cellphone 컬럼을 추가하려고 하면
-- member 테이블 생성
CREATE TABLE IF NOT EXISTS member (
id BIGINT AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
cellphone VARCHAR(255) NOT NULL, // // 새로운 필드 추가
created_date_time DATETIME(6) NOT NULL,
updated_date_time DATETIME(6) NOT NULL,
PRIMARY KEY (id)
);
에러 메시지는 아래와 같습니다.
ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 1
-> Applied to database : 1825070444
-> Resolved locally : 702378192
Either revert the changes to the migration, or run repair to update the schema history.
Need more flexibility with validation rules? Learn more: https://rd.gt/3AbJUZE
이 오류는 Flyway가 이미 적용된 마이그레이션 파일을 다시 체크했을 때, 원본 파일과 변경된 파일의 체크섬이 달라지기 때문에 발생합니다.
따라서, 스키마에 대한 모든 변경은 반드시 새로운 버전의 마이그레이션 스크립트를 추가하는 방식으로 처리해야 합니다. 이미 적용된 마이그레이션 파일을 수정하지 않고, 새로운 파일을 생성하여 변경 사항을 적용하는 것이 올바른 방법입니다. (-> V2__member_add_cellphone.sql
와 같이 새로운 버전의 마이그레이션 스크립트를 추가해야 합니다)
실무에서 리눅스 환경의 shell script 를 보면서 새롭게 알게된 /dev/null 2>&1
에 대해 정리하고자 한다.
/dev/null 2>&1
에 대해 알기 전에, 그 전에 리다이렉션, 파일 디스크립터에 대해 간단히 정리해보자.
리다이렉션
(입출력 재지정)이란 일반적인 키보드 입력, 화면 출력을 사용하지 않고 사용자가 직접 입력과 출력을 재지정한다는 의미이다.
기본적으로 표준 입력은 키보드에서 직접 받아오고, 표준 출력은 화면에 출력된다.
이러한 기본적인 하드웨어적인 입출력 동작을 변경하는 것이다.
그래서 키보드에서 입력을 받지 않고 파일 내용을 입력으로 받거나, 내용을 화면에 출력하지 않고 파일에 저장하는 것이다.
리다이렉션
에 대한 기호, 방향, 의미를 정리하면 아래와 같다.
리다이렉션 기호 , 방향 , 의미
>
, 표준 출력 , 명령 > 파일
: 명령의 결과를 파일로 저장
>>
, 표준 출력(추가) , 명령 >> 파일
: 명령의 결과를 기존 파일 데이터에 추가
<
, 표준 입력 , 명령 < 파일
: 파일의 데이터를 명령에 입력
파일 디스크립터
는 시스템에서 할당 받은 파일을 대표하는 0이 아닌 정수 값으로, 쉘 이나 시스템 프로그래밍에서 자주 접할 수 있다.
즉, 음수가 아닌 0과 양수인 정수 값을 가진다.
유닉스 시스템은 일반적인 파일부터 디렉토리, 소켓, 파이프 등 모든 객체들을 파일로 관리하는데, 쉘은 작업 중 필요한 파일에 일련번호를 붙여서 관리한다.
이 일련번호가 파일 디스크립터
이며, 표준 입출력 장치도 이 일련번호로 제어된다.
기본적으로 3가지(input, output, error)가 있다.
표준 입력(Standard Input, STDIN): 명령어에 입력될 내용을 저장 / 일련번호: 0
표준 출력(Standard Output, STDOUT): 명령어에서 출력될 내용을 저장 / 일련번호: 1
표준 오류(Standard Error, STDERR) : 명령어에서서 출력될 에러 메시지를 저장 / 일련번호: 2
그래서 사용자는 이 일련번호
를 이용하여 입출력 장치를 리다이렉션할 수 있는 것이다.
위에서 설명한 리다이렉션의 기호와 파일 디스크립터
를 가져와서 해석해보면 다음과 같다.
$ 2>&1
2>&1
는 표준 에러를 표준 출력(&1) 으로 리다이렉션(재지정)한다는 의미이다.
위의 명령어를 제대로 알기 전에, 몇 가지 예제로 정리해보자.
정상적이지 않은 명령어인 경우
$ ls -ERR 2> log.txt
ERR
라는 리눅스에 존재하지 않는 옵션을 주면, 에러가 발생하게 되는데, 이러한 에러 메시지를 화면에 출력하는게 아니라 log.txt
라는 파일에 저장하는 것이다.
이렇게 작성하면, 만일 프로그램이 에러가 나더라도 화면에 에러 메시지가 나오지 않고, 파일에 기록되어서 나중에 확인할 수 있다는 점이다.
정상적인 명령어인 경우
$ ls -al 2> log.txt
정상적인 명령어를 입력한 경우에는 조회해보면 아무런 내용이 들어가 있지 않다.
즉, 2>
표준 에러에 대한 리다이렉션은 에러 메시지만 취급하기 때문에 정상적인 명령에 대해서 아무런 작업을 하지 않는 것이다.
명령어가 성공해도 파일에 내용을 넣고, 실패해도 파일에 내용을 넣고 싶은경우 -> 한 번에 기록하고 싶다면 어떻게 해야할까?
$ ls > log.txt 2> log.txt
# 만일 명령어를 실행해서 성공하면 표준 출력 재지정을 통해 파일에 기록하고
# 명령어가 실패하면 표준 에러 재지정을 통해 파일에 기록하도록 조정
명령어가 성공하면 결과 내용이 파일에 기록되고, 명령어가 실패해도 에러 메시지가 파일에 기록될 수 있다.
여기서 2>&1
은 위의 명령어를 함축한 버전이라고 보면 된다.
$ ls > log.txt 2> log.txt #이전 버전
$ ls > log.txt 2>&1 #함축한 버전
&
: &1
에서 &
부분은 숫자 1을 표준 출력 일련번호로 인식하게 해주는 설정이다. 만일 &
기호를 안쓰게 되면 파일로 읽혀 버리기 때문이다.
2>
: 표준 에러를 표준 출력(&1)으로 재지정한다는 의미이다.
$ nohup.out > /dev/null 2>&1
여기서 /dev/null
는 블랙홀이라고 이해하면 된다.
이 경로에 보내지는 모든 파일과 데이터들이 블랙홀에 빨려들어가 (없을)무가 된다.
즉, /dev/null
로 결과를 보내는 것은 데이터를 모두 지워서 화면에 표시하지 않는다는 것을 말한다.
따라서 출력이 필요 없는 경우 > /dev/null
로 리다이렉션 시키면 된다.
결론적으로 > /dev/null 2>&1
은 표준 출력과 표준 에러를 모두 지우겠다는 의미이다.
글또는 글 쓰는 개발자 모임이며, 이번 9기에 활동했던 인원은 약 450명일 정도로 다양한 개발자 모임들 중에 큰 규모로 운영중인 모임이지 않을까 싶어요.
글또의 비전이 글을 작성하는 개발 직군분들이 모여서, 좋은 영향을 주고 서로 같이 자랄 수 있는 커뮤니티인 만큼 저도 이번 9기에 참여하면서 정말로 다양한 경험과 배움을 얻었어요.
글또에 알기 전에는 그동안 혼자서 공부한 것들을 제 개인 기술 블로그에 정리하면서 지냈던 것 같아요.
그렇게 약 2년이 지났을 때쯤, 2023년 11월부터는 서서히 제 글 쓰는 능력에 대한 부족함을 느꼈고, 그러한 점들을 어떻게 하면 개선해 나갈 수 있을까를 고민하던 찰나에, 어떤 개인 기술 블로그에 나와있는 내용을 통해 글또를 알게 되었어요.
운이 좋게도 저의 지원서를 좋게 봐주셔서 9기에 활동할 수 있게 되었고, 그렇게 2023년 12월부터 4월까지 5개월간 참여하게 되면서 제가 느꼈던 점들을 하나씩 정리해보려고 해요.
글또에 대한 궁금한 점들은 글또 소개 페이지에서 확인해주시면 될 것 같아요 ! (기존에는 Notion
으로 되어져있었는데, 이번 9기에서 글또 운영진분들의 열정과 노력 덕분에 홈페이지
로 개선되었어요)
글또를 5개월간 활동하면서, 필수적으로 지켜야 할 점은 2주에 1개의 글을 작성한 뒤에 슬랙을 통해 글을 제출하는 형식이었어요.
글또를 막 시작했을 12월에는 ‘2주에 한번 글을 쓰고 제출하는 거면 충분히 할 수 있겠네 !’ 라고 다짐했었는데, 글또 9기를 활동하는 중간에 스타트업 회사로 옮기면서, 그 다짐을 지키지 못했어요 🥲.
그래서 총 10회 차에 7번 제출, 2번 패스를 사용했고, 마지막 주차에는 제출하지 못했어요.
지금 돌이켜 생각해보니, 제출하는 글 내용에 대한 퀄리티에 제 스스로 너무 생각을 많이 했던 것 같아요.
초반에 3개월 동안(2023.12 ~ 2024.02)은 글 하나를 작성하는데 평균 10-12시간 정도 걸렸던 것 같아요. (예시 [2023년 회고] 다양한 활동으로 가득한 특별한 한 해 - 글 작성하는데 걸린 시간: 11H) 후반(나머지 2개월)에는 새로운 회사에 대한 적응하는 것과 글 쓰는 것에 대한 부담감, 그 외에 다른 여러 가지 일들로 인해 글 제출을 많이 못했어요.
이미 지나가버린 과거이지만, 다음에는 적당한 분량과 시간을 조절하면서 글을 작성하려고 해요 ✨. 무엇보다도 퀄리티에 신경써서 제출을 아예 안하는 것보다 조금이라도 작성해서 제출하는게 더 낫다는게 제 생각이에요.
글또에는 글 작성하는 게 필수이지만, 그 외에 다양한 활동은 본인의 의지로 할 수 있어요!
대표적으로 저는 아래와 같은 활동들을 했어요.
백엔드/인프라 빌리지 반상회
에 준비위원회로 참여 - ‘글또’ 키캡 제작, 음식 선정 및 주문커피챗
- (온/오프라인) 총 4
번 참여모닝또
- 기상 시간과 함께 오늘 할일을 공유하는 채널몸또
- 운동과 관련된 걸 기록하고 공유하는 채널커또
- 커피를 좋아하는 사람들이 모여서 카페 추천이나 커피 원두를 공유하는 채널월간-메이커스
- 한달 동안 1인 개발자로 본인이 원하는 주제에 맞는 사이드 프로젝트를 기획부터 개발해서 공유하는 채널 / 2
개의 사이드 프로젝트 개발TIL
- 오늘 하루에 IT
와 관련된 기술/협업에 대한 걸 정리하고 공유하는 스터디 / 2023.12 ~ 현재 진행중포폴-이력또
- 한달 간의 이력서와 포트폴리오를 다듬으면서 다양한 직무간의 사람들이 서로 피드백하는 스터디(백엔드/인프라 빌리지 반상회
, TIL
, 포폴-이력또
에 대한 자세한 후기는 2024년 1분기 회고에 작성해두었어요)
커피챗
은 회사 도메인/직무/커리어 목표/취미 등 다양한 주제로 대화를 나누고 있어요.
오프라인으로 커피챗을 하면서, ‘본인의 삶과 자기개발에 열정적이고, 재밌게 사시는 분들이 많구나‘를 느꼈어요. 그리고 커피챗에서 만난 분들이 성격과 마인드가 다 좋으셔서 ‘어떻게 다 이런 분들만 있으시지?’ 라는 생각에 많이 놀랐어요.
그렇다고 전부 다 열정적인 분들은 아니니, 너무 부담갖지 않으셔도 되세요. 오히려 그러한 분들이 있으셔서 저도 많은 자극과 동기부여를 받았고, 덕분에 긍정적인 에너지를 많이 받으면서 잘 지내고 있습니다 😊.
실제로 저는 포폴-이력또
를 통해 AI 스타트업 회사에 백엔드 개발 직무로 합격해서 현재 다니고 있어요.
해당 스터디를 운영해주신 YH님께 이 자리를 빌어 감사드리고, 그 외에 저의 이력서에 피드백을 해주신 스터디원 분들, 그리고 JY님, NY님께도 감사의 말씀 전합니다.
(포폴-이력또
에 대한 진행 과정이 궁금하시다면, YH님이 개인 블로그에 올려주신 이력또 : 한달 간의 이력서 정리 스터디 진행 후기에 참고해 주시면 감사하겠습니다)
최근에는 새로운 회사의 업무에 집중하느라 TIL
스터디 하나만 참여하고 있지만, 나중에 어느 정도 시간이 된다면 다른 스터디 혹은 취미 활동에 참여하려고 해요.
글또에 대한 아쉬운 점 보다는 제 자신에 대한 아쉬운 점이 있어요.
먼저, 위에서 언급한 것처럼 글 제출에 대한 부분이에요. 회사에 업무도 중요하지만, 하루에 몰아서 글을 작성하는 것보다는 매일 10분이라도 조금씩 글을 작성하면서 마감 기한에 맞춰서 글을 제출할 거에요. 그래서 왠만하면 패스 없이 글을 제출하는 것이 목표입니다.
그리고 월간-메이커스
와 같이 사이드 프로젝트에 대한 개발 부분이에요.
12-1월에 혼자서 사이드 프로젝트 2개를 개발부터 배포를 했지만, 디테일한 부분과 수익성 부분이 아쉬웠어요.
만약 10기에도 월간-메이커스
와 같은 채널이 있다면, 개발에 대한 기술 뿐만 아니라 비즈니스 부분도 생각하면서 서비스 출시를 목표로 할 생각입니다.
월간-메이커스
채널을 만들어주시고 운영해주신 다코(daco)님께 감사의 말씀 전합니다. (TIL
스터디 오래오래 같이 열심히 해봐요 🙌🏻)
(다코님의 블로그 제목은 코드로 우주평화 입니다)
마지막으로 글또에 조금이나라 기여를 하고자 해요.
글또 9기에는 백엔드/인프라 빌리지 반상회
에 준비위원회로 참여했지만, 반상회가 아닌 기술적으로 도움을 드리고 싶어요.
아직 무엇을 도움을 드리고자 할지에 대해서는 더 생각을 해봐야겠지만, 취업준비생분들을 위한 온/오프라인 커뮤니티 모임을 개발로 만들면 어떨까 싶어요.
저도 작년까지만 해도 취업준비생 시절을 보내왔지만, 생각보다 현업 개발자분들을 만나기가 쉽지 않았기 때문에, 이러한 커뮤니티 모임이 있으면 좋을 것 같다는 생각이 들었습니다.
위의 아쉬운 점들을 요약하면 아래와 같아요.
제가 생각하기에 글또
라는 모임은 단순히 글을 작성하는 스킬 향상에 그치지 않고, 그 외 다양한 활동이 있고 그러한 점들을 통해 개인적인 성장 뿐만 아니라 서로가 도와주면서 함께 성장하는 분위기 같아요.
시간적 여유가 되신다면 다음 글또 10기에 참여하시는 걸 적극 추천드립니다.
(위에는 예시이고, 실제로 이 밖에도 다양한 활동들이 있습니다)
지금까지 이 글을 읽어주셔서 감사합니다 😌.
글 작성하는데 걸린 시간: 3시간
그동안 1년 단위로 회고를 진행해왔는데, 최근 3개월은 여러가지 활동들을 보내서 처음으로 분기별 회고를 작성해보려고 합니다.
1월부터 3월까지 공부한 내용은 다음과 같습니다.
“스프링 DB 1편 - 데이터 접근 핵심 원리” 강의 수강 및 정리
“자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)” 강의 수강 및 정리
“실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)” 강의 수강 및 실습
“가상 면접 사례로 배우는 대규모 설계 시스템 기초” 책 읽고 정리
스프링과 코틀린에 대해 강의를 통해 학습하고 개인 포스팅으로 정리하는 식으로 진행했습니다. 또한 최근에는 설계 시스템에 관심이 가게 되어서 “가상 면접 사례로 배우는 대규모 설계 시스템 기초” 라는 책을 읽었습니다.
사이드 프로젝트 진행기간: 2023.12 ~ 2024.02
히빗 프로젝트는 백엔드 부분에서의 코드와 구조를 개선하기 위해 혼자서 다시 진행한 프로젝트입니다. 이와 관련된 부분은 다른 포스팅에 이미 작성해둔 게 있기 때문에, 여기서는 생략하겠습니다.
아래 링크를 통해서 더 자세히 확인하실 수 있습니다.
(히빗 V2 업그레이드: 백엔드 개선과 개발자 성장기)
TIL 진행기간: 2024.01 ~ 현재 진행중
TIL 모임은 글또에서 활동하고 계신 NY님이 올린 모집글을 통해 신청하게 되었습니다.
처음 1월에는 Slack 채널에서 비공개로 진행했었는데, 대표적으로 2가지 단점이 보이기 시작했습니다.
슬랙 유료 멤버십을 가입하지 않으면, 3개월 이후에는 블러 처리가 되어서 기록된 내용을 볼 수 없게 됩니다.
온라인 화상회의를 하려면 다른 플랫폼을 이용해야 한다는 점이 있습니다.
무엇보다도 ‘기록’을 열심히 했음에도, 나중에 볼 수 없다는 점이 가장 큰 단점 이었기 때문에, 2월부터는 디스코드로 갈아탔습니다.
디스코드는 무료로 기록도 되었고 내부에서 화상 회의가 가능했기 때문에, 위의 2가지 단점을 모두 커버할 수 있었습니다.
1월에는 6명이서 시작했던 TIL 모임이 이제(3월 기준)는 12명이 되어갔습니다.
2월부터는 TIL 인증 뿐만 아니라, 매주 금요일 혹은 일요일 저녁에 30분 정도 ‘주간 회고’를 진행하면서 KPT 회고를 작성하고, 서로 얘기하는 시간을 가졌습니다.
확실히 이런 TIL 모임을 하니까, 다른 구성원분들로부터 자극을 받고, 스스로 동기부여를 하면서 매일 공부하는 습관을 유지할 수 있었던 점이 좋았습니다.
1월부터 3월까지 3개월 동안 한 결과, 총 60번의 제출 중 41번을 제출하였습니다. 제출하지 못한 날이 꽤 있어서 아쉽긴 하지만, 그래도 제출한 내용 만큼은 현재뿐 아니라 앞으로도 도움이 되는 기술적 지식 또는 경험 위주로 정리하려고 노력했습니다.
이전까지는 1달 단위로 TIL 모임을 운영해왔지만, 앞으로는 분기별로 진행하면서 꾸준히 공부하면서 성장해가는 개발자가 되도록 노력할 예정입니다 😌.
스터디 진행기간: 2024.01 ~ 2024.02
글또에서 활동하고 계신 YH님께서 “이력서, 포트폴리오 개선 스터디원”을 구하신다는 모집글을 보고, 바로 참여하게 되었습니다.
평소에 이력서에 대한 피드백을 동료분들 혹은 저보다 연차가 많은 개발자분들로부터 받아왔지만, 아직도 부족하다고 느끼고 있었습니다. 또한 제가 조금이라도 도움이 드릴 수 있는 부분이 있다면 적극적으로 도와드리고 싶었습니다. 해당 스터디를 통해 더 나은 이력서를 만들고자 참여를 하게 되었습니다.
YH님이 주도적으로 스터디를 이끌어주시면서 어떻게 진행할지 알려주셨고, 더 나은 방법이 있다면 스터디에 참여하는 구성원분들이 의견을 제시해서 조금씩 개선하는 방향으로 나아갔습니다.
피그마를 이용하여 2주마다 총 3개의 버전을 만들면서 이전 대비 더 나아지는 이력서를 만드는 것이 목표였습니다.
2주 간격으로 온라인 회의(Google Meet)를 진행하면서, YH님이 그때마다 필요한 내용들을 Notion에 정리해주셔서 구성원 모두에게 공유하고 얘기하는 시간을 가졌습니다. 해당 스터디 이후에 서류에 합격하는 사람이 있으면, 위와 같이 스터디원 분들이 모두 축하해줬습니다.
스터디 마지막 날에는 이번 모임의 목적을 다시 생각하면서, 부족했던 부분, 좋았던 부분, 개선할 부분들을 정리하는 시간을 가졌습니다. 다음에 또 이력서 스터디를 모집한다면, 지금보다 더 효과를 누릴 수 있지 않을까 생각합니다.
피그마에서 각 직군별로 분류하였고, 본인의 직무에 있는 곳에 이력서를 올리는 식으로 진행했습니다. 그리고 2주 동안 본인의 이력서를 다른 버전으로 업데이트 하는 동시에, 다른 사람의 이력서를 보고 피드백하는 식으로 했습니다.
저는 해당 스터디를 시작하기 전부터 기존의 이력서가 있었고, 스터디를 진행하면서 서류합격 받은 회사들로부터 면접을 진행하고 있었습니다. (이때가 정말 바빴고, 정신없이 시간을 보냈던 것 같습니다 😂)
그렇게 열심히 노력한 결과, 운이 좋게도 한 회사에 최종합격을 받게 되었습니다. 일부 기술 면접 질문에 대해 제대로 답변을 못한 경우도 있었는데, 그러한 부분을 관대하게 넘어가주셨고, 인성 부분에서 좋게 봐주셔서 ‘합격’해주셨다고 생각합니다. 앞으로 새로운 회사에 다니면서 꾸준히 성장해서 더 나은 개발자가 되도록 노력하겠습니다.
글또에 참여하면서, 글을 제출하는 것 외에 글또에 도움이 되는 활동에 참여하고 싶다는 생각이 들었습니다.
그렇게 생각만 하다가 어느 날, 백엔드/인프라 반상회를 위한 준비 위원회 모집 참여글을 확인하게 되었고, 참여 의사를 전달하면서 준비위원회에 합류하게 되었습니다.
첫 미팅은 온라인으로 진행 했고, 두 번째 미팅은 오프라인(우아한형제들, 선릉역 부근)에서 진행되었습니다. 성윤님이 PO 역할로 큰 부분을 잡아주셨고, 세부적인 부분들은 팀을 꾸려서 전개해 나가도록 진행했습니다.
저는 굿즈팀에서 키캡을 제작하는 걸 담당했고, 음식팀에서는 TJ님과 메뉴 선정부터 배송 과정까지 기획하는 걸 담당했습니다. 키캡을 제작하면서, 굿즈팀의 YE님께서 디자인 부분을 많이 도와주셔서 일사천리로 끝내서 정말 감사했습니다. 음식팀에서는 TJ님이 적극적으로 임해주셨고, 성윤님이 뒤에서 많은 부분을 도와주셔서 감사했습니다.
결과적으로 키캡은 위와 같이 제작되었습니다.
그리고 굿즈팀의 YE님과 SM님이 포스터를 정성스럽고 이쁘게 만들어주셨습니다. (비록 백엔드 직무이시만, 취미로 디자인을 하시는 YE님 짱입니다..!) 이 외에도 여러 팀에서 본인이 맡으신 부분들을 적극적으로 임해주셔서, 결과적으로 백엔드/인프라 반상회 준비가 문제없이 잘 되었다고 생각합니다.
3월 14일, 반상회 당일 ‘마루 180’이라는 장소에서 100명이 넘는 글또 분들이 오프라인에 참석하면서 오프닝 -> 발표 -> 네트워킹 -> 기념 사진 촬영 으로 진행하게 되었습니다.
백엔드/인프라 반상회를 한 마디로 정리하면, ‘짧지만 강렬했고 즐거웠던 네트워킹 모임’ 입니다.
해당 반상회에 대한 자세한 내용이 궁금하시다면, 승현님이 작성한 포스팅에서 확인하시면 좋을 것 같습니다 ! 여유가 된다면, 저도 이 부분에 대해 나중에 포스팅으로 업로드할 예정입니다.
4월부터는 새로운 회사에서 백엔드 직무로 일하게 되었는데, 우선 회사에 적응을 하면서 도메인과 기술 스택에 빠르게 적응하는 시간이 될 것 같습니다.
그런 다음에, 백엔드 성장에 필요한 기술들을 별도로 공부하면서 지낼 것 같습니다.
(해당 포스팅은 추후에 내용을 추가 및 보완할 예정입니다.)
지금까지 이 글을 읽어주셔서 감사합니다. 😌
글을 작성하는데 걸린 시간: 약 3시간
이 글은 실제 히빗 프로젝트(ver.2)를 혼자서 개발하면서 경험한 내용을 정리한 글입니다.
이와 관련해서 코드에 대한 부분은 PR에 있습니다.
Swagger
는 API 동작을 테스트하는 용도에 더 특화되어 있습니다.
그래서 프론트와 백엔드에서 테스트 유무를 확인할 때는 용이했습니다.
기존 version1 에 개발했던 Hibit은 Swagger
로 API 문서를 만들었습니다.
그러나 Swagger
를 적용하면서 백엔드 코드에 Swagger
와 관련된 어노테이션이 추가되는데, 이로 인해 코드 내부에 기능과 문서가 혼합되어 깔끔하지 않은 코드가 되었습니다.
예를 들어, 게시글을 등록하는 API 부분의 코드는 다음과 같습니다.
기존 version1 - PostController
@Tag(name = "posts", description = "게시글")
@RestController
public class PostController {
private final PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
@PostMapping("/api/posts/new")
@Operation(summary = "/api/posts/new", description = "매칭 게시글 작성")
@Parameters({
@Parameter(name = "title", description = "제목", example = "디뮤지엄 전시 보러가요"),
@Parameter(name = "exhibition", description = "가고싶은 전시회", example = "오스틴리 전시회"),
@Parameter(name = "exhibitionAttendance", description = "전시 관람 인원", example = "4"),
@Parameter(name = "possibleTime", description = "관람 희망 날짜", example = "[\"2023-12-25\"]"),
@Parameter(name = "openChatUrl", description = "오픈 채팅방 URL", example = "http://kakao"),
@Parameter(name = "togetherActivity", description = "함께 하고싶은 활동", example = "EAT"),
@Parameter(name = "content", description = "상세 내용", example = "오스린리 전시회 보러가실 분 있나요?"),
@Parameter(name = "postImages", description = "게시글 이미지 리스트 URL", example = "[\"image1\", \"image2\", \"image3\"]")
})
public ResponseEntity<Post> save(@Parameter(hidden = true) @AuthenticationPrincipal final LoginMember loginMember, @RequestBody PostCreateRequest request) {
Post post = postService.save(request, loginMember.getId());
return ResponseEntity.status(HttpStatus.CREATED).body(post);
}
}
이런 형태의 코드는 Swagger
를 사용할 때 발생하는 문제를 나타냅니다.
따라서 이러한 이유로 version2 프로젝트에서는 Swagger
보다는 깔끔하고 명료한 문서를 생성할 수 있는, 주로 문서 제공 용도로 사용되는 Spring Rest Docs
로의 전환을 결정하게 되었습니다.
Spring Rest Docs의 특징으로는 다음과 같습니다.
테스트 코드를 통한 API 문서 자동화 도구입니다. (제가 만든 api 명세를 테스트 코드를 작성해서 build하면 문서처럼 보여지는 것)
API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원활하게 합니다.
기본적으로 AsciiDoc
을 사용하여 문서를 작성합니다. (markdown 같은 것)
Rest Docs와 Swagger의 장점과 단점을 비교하면 다음과 같습니다.
Rest Docs
장점
테스트를 통과해야 문서가 만들어진다. (신뢰도가 높다)
프로덕션 코드에 비침투적이다.
단점
코드 양이 많다.
설정이 어렵다.
Swagger
장점
적용이 쉽다.
문서에서 바로 API 호출을 수행해볼 수 있다.
(UI에 대해) 알록달록하게 문서를 작성할 수 있다.
단점
프로덕션 코드에 침투적이다.
테스트와 무관하기 때문에 신뢰도가 떨어질 수 있다. → 실제로 동작하는 것과 문서가 따로 돌 수 있다.
현재 히빗 version2 프로젝트는 아래와 같은 기술을 사용하고 있습니다.
Language
- Java 11
Framework
- Spring Boot 2.7.1, Spring MVC 5.3.2
ORM
- Spring Data JPA 2.7.1, JPA Hibernate 5.6.1
Database
- H2, MySQL 7.4
Build Tool
- Gradle 8.0
Test
- Junit 5, Mockito 4.5.1
Rest Assured
Rest Assured
는 별도의 구성 없이는 @SpringBootTest
로 수행해야 합니다.
@SpringBootTest
는 스프링의 전체 컨텍스트를 로드하여 빈을 주입하기에 속도가 많이 느립니다.
즉, Rest Assured
는 스프링 애플리케이션이 동작하는 실제 환경과 거의 동일한 환경에서 테스트를 진행할 때 사용됩니다.
그래서 @SpringBootTest
을 적용한 RestAssured
는 속도가 느리고, 비용이 많이 듭니다.
MockMvc
반면에 MockMvc
는 @SpringBootTest
와 함께 사용할 수 있고, @WebMvcTest
와 함께 사용할 수도 있습니다.
@SpringBootTest
다르게 @WebMvcTest
는 Controller Layer(=Presentation Layer) 만 테스트 하기에 속도가 빠릅니다.
(@WebMvcTest
사용할 때는 보통 서비스 계층을 Mocking을 하여 작성하기 때문입니다)
결론
만약 통합테스트를 한다면 Rest Assured
가 좋은 선택이지만, Spring Rest Docs로 문서를 작성하는 데에는 MockMvc
가 더 나은 선택이라 생각하여 MockMvc
기반으로 진행하게 되었습니다.
저에게 익숙한 Markdown
이 작성하기 더 편할 수 있지만, Markdown
으로 Rest Docs를 작성하려면 Ruby 프로젝트인 slate가 필요합니다. (include가 되지 않아 별도로 설치해야 합니다)
slate
는 Gradle로 관리되는 프로젝트가 아니기 때문에, 해당 프로젝트를 직접 다운로드 받아서 진행해야 합니다.
이는 곧, slate
에 의존해야만 하는 구조가 되고, Ruby 의존성들이 추가로 생성되면서 빌드 시간이 전체적으로 오래 걸리게 됩니다.
또한 개인적으로 slate
로 만들어진 UI가 저에게 친숙하지 않았습니다.
반면에, Asciidoc
는 문서를 작성할 수 include 기능을 제공하며(Gradle로 관리됨), 표현할 수 있는 문법들이 많이 있습니다.
그리고 UI도 slate
에 비해 깔끔하면서 이뻤습니다.
그래서 Asciidoc
을 사용하기로 했습니다.
MockMvc
, Asciidoc
기반 Rest Docs 설정 - build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.10'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'jacoco'
id "org.asciidoctor.jvm.convert" version "3.3.2" // (1) plugin 추가
}
group = 'com'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '11'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
asciidoctorExt // (2) asciidoctor의 Extention에 대한 configuration 추가
}
repositories {
mavenCentral()
}
dependencies {
// Spring boot
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// ...
// spring restdocs (3), (4)
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
}
ext { // (5) 문서 조각들에 대한 경로를 지정
// 전역 변수
snippetsDir = file('build/generated-snippets')
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
test {
outputs.dir snippetsDir // (6) 테스트에서 테스트가 끝난 결과물을 snippetsDir 에 넣음
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}
// (7) asciidoctor 태스크에 대한 설정
asciidoctor {
inputs.dir snippetsDir // 불러올 스니펫 위치를 snippetsDir로 설정
configurations 'asciidoctorExt' // Asciidoctor 확장에 대한 설정
sources { // 특정 파일만 html로 만듬
include("**/index.adoc")
}
baseDirFollowsSourceFile() // 다른 adoc 파일을 include 할 때 경로를 baseDir로 맞춤
dependsOn test // test 태스크 이후에 asciidoctor를 실행하도록 설정
}
Asciidocotor
는 Asci 파일을 html 파일로 변환해주는 도구 입니다.
(1) AsciiDoc 파일을 컨버팅하고 Build 폴더에 복사하기 위한 플러그인을 추가합니다.
(2) asciidoctor의 Extention(확장)에 대한 Configuration 구성을 추가합니다.
(3) asciidoctorExt
에 spring-restdocs-asciidoctor 의존성을 추가합니다. 이 종속성이 있어야 build/generated-snippets
에 있는 .adoc
파일을 읽어들여 .html
파일로 만들어낼 수 있습니다.
(4) MockMvc
를 사용하기 위한 spring-restdocs-mockmvc 의존성을 추가합니다.
(5) 생성되는 스니펫들이 생성되는 디렉터리 경로를 설정합니다.
(6) 테스트가 끝난 결과물을 snippetsDir
에 넣습니다.
(7) asciidoctor
태스크에 대한 설정을 합니다.
Preference - Plugins -
AsciiDoc
설치
ControllerTestSupport
@AutoConfigureRestDocs // (1)
@WebMvcTest(controllers = {
MemberController.class,
AuthController.class,
ProfileController.class,
PostController.class
})
@Import(ExternalApiConfig.class)
@ActiveProfiles("test")
public abstract class ControllerTestSupport {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
// ...
@MockBean
protected PostService postService; // (2)
}
PostControllerTest
class PostControllerTest extends ControllerTestSupport {
private static final String AUTHORIZATION_HEADER_NAME = "Authorization";
private static final String AUTHORIZATION_HEADER_VALUE = "Bearer aaaaaaaa.bbbbbbbb.cccccccc";
@DisplayName("등록된 게시글을 모두 조회한다.")
@Test
void 등록된_게시글을_모두_조회한다() throws Exception {
// given
Member 팬시 = 팬시();
팬시 = memberRepository.save(팬시);
List<Post> 게시글_목록 = List.of(프로젝트_해시테크(팬시), 오스틴리_전시회(팬시));
PostsResponse response = PostsResponse.of(게시글_목록);
given(postService.findAll()).willReturn(response); // (3)
// when & then
mockMvc.perform(get("/api/posts")
.header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andDo(document("posts/find/all/success",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())
))
.andExpect(status().isOk()); // (4)
}
}
(1) @AutoConfigureRestDocs
어노테이션은 Spring REST Docs를 자동 구성해주는 어노테이션입니다. 이를 적용하게 되면 테스트 케이스 실행시 자동으로 API 문서가 생성됩니다.
@AutoConfigureRestDocs
에 uri 정보가 선언되어 있으면 적용하고, 없으면 기본 설정값으로 적용(2) PostService
의 구현체를 mocking하기 위해 @MockBean
을 선언합니다.
(3) 테스트에서 given 메소드를 사용하여 postService.findAll()
메소드 호출 시 PostsResponse
객체를 반환하도록 설정합니다.
(4) 테스트에서 mockMvc.perform()
메소드를 사용하여 실제 HTTP 요청을 모의로 실행하고, 그 결과를 검증합니다. document 메소드를 통해 요청과 응답을 문서화합니다.
이러한 과정을 통해 Spring REST Docs를 활용하여 API 문서를 생성할 수 있습니다.
오른쪽 Gradle - Tasks - documentation - asciidoctor 실행하면 성공이 되고,
build 안에 posts
폴더가 생기고, find/all/success
폴더 안에 문서 조각들이 생깁니다.
이 다음에 해야할 작업은 문서 조각을 하나로 합쳐서 하나의 문서로 만드는 작업 입니다.
src 폴더 하위에 docs 라는 폴더를 만든 뒤, 그 안에 asciidoc 폴더를 생성한 뒤에 index.adoc
이라는 파일을 만듭니다.
index.adoc
파일 안에 아래와 같이 작성하면 미리보기를 통해 확인할 수 있습니다.
오른쪽 Gradle - build (또는 documentation - asciidoctor)를 눌러주면, 왼쪽 docs 폴더 - asciidoc 폴더 안에 index.html
파일이 생성됩니다.
그리고 Chrome 창으로 열어보면 아래와 같이 API 문서가 나오게 됩니다.
Spring Rest Docs를 Hibit version2 프로젝트에 통합하는 과정을 통해, 초기 설정부터 실제 적용까지의 단계를 체계적으로 이해하게 되었습니다.
이 과정에서 Asciidoc 문법의 기초를 배웠음에도 불구하고, 아직 배워야 할 내용이 많다는 것을 깨달았습니다.
앞으로는 Spring Rest Docs의 활용 방법에 대해 더욱 개방적인 태도로 접근하며, 이를 통해 지식을 확장하고 실무 경험을 축적해 나가겠습니다.
해당 부분에 대한 코드는 Github(PR) 에서 확인할 수 있습니다.
지금까지 읽어주셔서 감사합니다.
글또 9기를 시작하고 4회차가 지난 이후에 글또의 운영진이신 성윤님께서 ‘글쓰기’ 라는 주제로 세미나를 해주셨어요.
해당 세미나에서 다룬 주로 내용은 다음과 같아요.
글쓰기가 어려운 이유
글쓰기의 핵심 요소
글을 쓰는 과정
글쓰기 전략
해당 세미나을 들으면서 지금까지 고수해왔던 저의 글쓰기 방식의 미흡한 부분과 개선점에 대해 고민해보게 되었습니다.
해당 세미나를 듣기 이전에는 저는 아래와 같은 순서로 글을 작성했었어요.
소재 탐색 및 정리 - 평소에 생각나는 주제들을 Notion
에 정리하기
소재 공부 - 블로그에 업로드할 소재에 대해 깊이 있게 공부하기 (관련 도서, 공식문서, 블로그)
글 작성 - 목차를 구성한뒤 글을 작성 -> ChatGPT에게 문장이 자연스러운지 검토받으면서 수정하기
글 업로드 및 퇴고 - 해당 글을 블로그에 업로드 한 뒤에 몇 번의 퇴고를 반복하기
저의 글쓰기 파이프라인을 KPT 회고
로 정리하면 아래와 같아요.
Keep(좋았던 점)
세미나에서 공유해주셨던 글쓰기 파이프라인과 80% 정도 유사해서 신기하면서도 뿌듯했어요.
글을 작성할 때 구조적이면서 이해하기 쉬운 글을 작성하려고 주어진 시간 내에 깊게 공부하고, ChatGPT의 도움으로 업로드 하기 전에 여러번 수정했어요.
Problem(아쉬웠던 점)
2023년 하반기(8월)부터 위의 글쓰기 파이프라인을 적용했기 때문에, 그 이전 글의 퀄리티가 다소 떨어진다고 생각했어요.
제 블로그의 목적은 꾸준함
과 저만의 백과사전
이였기 때문에, 그 만큼 카테고리가 다양해요. 그래서 제 3자가 보기에 혼란스러울 수도 있다는 생각이 들었어요.
2023년에는 187개의 글을 업로드했으나, 양보다 질이 떨어진다는 느낌을 받았어요.
글또에 제출하는 글 하나를 작성하는 데 최소 6시간 ~ 12시간 정도 걸렸어요.
Try(개선할 점)
현재보다 조금 더 구체적인 파이프라인을 구성하여 퀄리티 있는 글을 작성하려고 노력할 예정이에요.
백준, 프로그래머스와 같은 문제 풀이 포스팅은 TIL
이나 다른 곳에 업로드할 계획이에요.
글쓰기 주제를 기술
, 트러블 슈팅 경험
, 회고
, 리뷰
위주로 좁힐 예정이에요.
글이 많거나 이해하기 어려울 경우, 직접 도식화를 그린 뒤에 추가하여 이해를 돕고자 노력할 예정이에요.
2022년부터 현재까지 저의 블로그의 목적이자 글쓰기 전략
은 꾸준함을 증명하고 저만의 백과사전이 되는 것이였어요.
그래서 새로운 무언가를 배울 때, 그게 시간이 지난 이후에도 필요한 지식 또는 경험이라면 포스팅을 작성해서 제 블로그에 업로드를 해왔어요.
저는 블로그에 대해 너무 고민을 많이 하면, 오히려 글을 작성하기가 두렵게 되고, 그게 지속되면 글을 작성 안하게 되더라고요.
그래서 단순하게 생각하는 편인데, 성윤님의 세미나 강연을 보면서 조금은 고쳐야 할 부분이 생겼어요.
글쓰기 파이프라인 개선: 글쓰는 주제에 대한 우선순위를 세우고, 그 기준에 맞게 순서대로 글을 작성하자.
이전의 글쓰기 파이프라인
을 보다 구체적으로 작성해서 퀄리티 있는 글을 작성하자.
주제 범위 좁히기: 글쓰기 주제를 기술
, 트러블 슈팅
, 회고
, 리뷰
위주로 글을 작성하여 일관성을 유지하자.
시간 제한과 몰입도 향상: 시간 제한을 설정해서, 시간안에 최대한 몰입해서 2~6시간 이내로 작성하자.
이해를 돕는 시각 자료 활용: 복잡한 내용은 그림과 같은 시각 자료를 통해 제 3자가 봐도 이해되기 쉬운 글이 되도록 작성하자.
이러한 개선을 통해 더욱 발전된 글쓰기를 해나갈 계획이에요.
최근에 작성한 “히빗 V2 업그레이드: 백엔드 개선과 개발자 성장기“에 대한 글을 수정했어요.
이전에는 제목
과 목차
가 아래와 같이 구성되어 있었어요.
수정하기 전
[제목] : 히빗 프로젝트 Version 1에서 2로의 전환: 백엔드 코드 개선과 개발자 성장의 여정
[목차]
## 과거의 '나'와 현재의 '나'
## version2의 목표
## 개선한 점
// ...
## 느낀점(아쉬운 점)
## Reference
제목
은 “SEO 관점 + 단순 명료하게” 되도록 수정하고, 목차
에 대해서는 서론과 결론에 대한 부분이 부족하다고 느껴 그 부분을 아래와 같이 추가했어요.
수정한 후
[제목] : 히빗 V2 업그레이드: 백엔드 개선과 개발자 성장기
[목차]
## 시작하며 <- 추가됨
## 과거의 '나'와 현재의 '나'
## version2의 목표
## 개선한 점
// ...
## 느낀점(아쉬운 점)
## 마치며 <- 추가됨
## Reference
그리고 포스팅에 대한 내용
은 저만의 말투가 섞여있어서 ‘제 3자가 이해못할 수도 있겠구나’ 생각했어요. 그래서 ChatGPT
를 적극 활용했어요.
ChatGPT
에게 단순히 질문을 던지는 방식보다는 “~~주제에 대해서 나는 아래와 같이 작성했는데, 해당 문장을 읽고 깔끔하고 자연스러운 문장으로 수정해줄래?” 와 같이 원하는 답변이 나올 때까지 구체적으로 질문했어요.
또한, 설명이 부족한 부분은 보충하고 이미지도 도식화해서 추가했어요.
개선할 점을 반영한 글쓰기 파이프라인을 그림으로 표현하면 아래와 같아요.
소재 정하기 - 출/퇴근 시간, 휴일에 어떤 주제를 작성할까 고민하기 / 유레카처럼 갑자기 생각나면 바로 Notion
에 정리하기
소재 보관하기 - 작성할 소재를 Notion
에 정리 + 우선순위에 맞게 배치하기 -> 해당 소재에 대해 깊게 공부하기
초안 작성하기 - 시간 제한과 방해 금지 모드를 설정하고 몰입하여 글을 작성하기 (시간 제한:2시간) -> 글을 어느 정도 작성하면 ChatGPT
피드백 받기
업로드 및 퇴고하기 - 해당 글을 블로그에 업로드 한 뒤에 24시간 이내로 퇴고하면서 지속적인 업로드하기 -> 퇴고할 때 ChatGPT
피드백 받기
글또의 글쓰기 세미나를 듣고 제 글쓰기 프로세스를 정리하고 도식화해봤어요.
현재까지의 개선 사항을 정리했지만, 나중에 개선할 점이 더 있다면 추가적으로 이 포스팅도 업로드할 예저잉에요.
글쓰는 전략과 방법에 대해 자세하게 알려주신 성윤님께 감사의 말씀 전하며 앞으로도 질 높은, 퀄리티 있는 글 작성하기 위해 노력하겠습니다 ! ✍🏻
글 작성하는데 걸린 시간: 3시간
초안 글 작성: 2시간 10분
ChatGPT 피드백 후 정리: 20분
도식화 추가: 20분