[FastAPI] 4. SQLAlchemy + Alembic ์กฐํ•ฉ์„ ์ด์šฉํ•œ Database Migration ๊ฐ€์ด๋“œ

๋ฐ˜์‘ํ˜•

์›น ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฐฐํฌํ•œ ๋’ค์— ๋ฐ˜๋“œ์‹œ ํ•œ ๋ฒˆ ์ฏค ๋”ฐ๋ฅด๋Š” ์—…๋ฐ์ดํŠธ ์‚ฌํ•ญ์ด ๋ฐ”๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ๋ณดํ†ต ์•„๋ž˜์˜ 3๊ฐ€์ง€๊ฐ€ ๋Œ€ํ‘œ์ ์ž…๋‹ˆ๋‹ค.

 

  • ์ธ์Šคํ„ด์Šค์˜ ์ด์ „

    ํด๋ผ์šฐ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค, ํ˜น์€ ๋‹ค๋ฅธ ๋ฆฌ์ „์œผ๋กœ์˜ ์ด์ „

  • ํŠน์ • Entity์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ ์š”์ฒญ

    ์šฐ๋ฆฌ ๋ถ€์„œ ํ˜น์€ ์šฐ๋ฆฌ ์„œ๋น„์Šค์—๋Š” ์ด ๊ธฐ๋Šฅ์„ ๋„ฃ๊ธฐ ์œ„ํ•ด ์ด ์นผ๋Ÿผ์ด ๋” ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์•„์š”.

  • ์ผ๋ถ€ ์˜์†๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํŠน์ • ๋ฐ์ดํ„ฐ๋กœ ๋ณ€๊ฒฝ

    ์ด์ œ๋Š” ์ด ๋ช…์นญ์„ ์ด ๋ช…์นญ์œผ๋กœ ๋ฐ”๊ฟ€๊ฑฐ์—์š”. ๋ชจ๋“  ์‚ฌํ•ญ์— ์ ์šฉ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ๊ฐํ•œ๋‹ค๋ฉด ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์ˆ˜๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ํ•˜๋‚˜ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ๋งŒ๋“ค๊ณ , SQL ์ฟผ๋ฆฌ๋ฅผ ์งœ๋ณธ ๋‹ค์Œ ์ฟผ๋ฆฌ ํ…Œ์ŠคํŠธํ•˜๊ณ , ํ™•์‹คํ•œ ์ฟผ๋ฆฌ๊ฐ€ ์ž‘์„ฑ๋˜์—ˆ๋‹ค๋ฉด ์ด๋ฅผ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์“ฐ๋Š” ๊ฒƒ์ด์ฃ .

 

๊ทธ๋Ÿฐ๋ฐ, ์ด๋Ÿฌํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ํ•˜๋‚˜ ๋‘˜ ์Œ“์ด๋‹ค ๋ณด๋ฉด ์ฒ˜์Œ์—๋Š” ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ–ˆ๊ณ , ์–ด๋– ํ•œ ์ ์„ ๋ณ€ํ˜•ํ–ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ถ”์ ์ด ์–ด๋ ค์›Œ์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ์„œ๋น„์Šค๋Š” ์ฒ˜์Œ์— ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ–ˆ๋‹ค๊ฐ€ ์ด๋Ÿฌํ•œ ์ ์ด ํ•„์š”ํ–ˆ์–ด์„œ, ํ˜น์€ ์š”์ฒญ์ด ๋˜์„œ ๋ผ๋Š” ๊ฒƒ๋“ค์„ ๊ธฐ๋กํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋˜ ํ•œ ๊ฐ€์ง€, ๊ฐœ๋ฐœ์ž๊ฐ€ Dev, Stage, Prod ์ด๋ ‡๊ฒŒ ๊ฐ™์€ ์„œ๋น„์Šค๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์œผ๋กœ ๋‚˜๋ˆ„์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์–ด๋–ค ํ™˜๊ฒฝ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ด๋ฃจ์–ด์กŒ๊ณ , ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธ์ด ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ˜น์—ฌ๋ผ๋„ ์ด๋ฏธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ง„ํ–‰๋œ ์ธ์Šคํ„ด์Šค์— ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ง„ํ–‰๋œ๋‹ค๋ฉด ์ค‘๋ณต์ด ๋ฐœ์ƒํ•˜๊ณ , ์ด๋กœ ์ธํ•œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™”์— ๋Œ€ํ•ด ์ผ๋ถ€ ๋ฐ์ดํ„ฐ ์†Œ์‹ค์— ๋Œ€ํ•œ ์›์ธ์ด ๋˜๊ธฐ๋„ ํ•˜๋ฉฐ ์˜คํžˆ๋ ค ์ƒ์‚ฐ์„ฑ์„ ๋–จ์–ด๋œจ๋ฆฌ๊ฒŒ ๋˜๊ฑฐ๋‚˜ ์‹ฌ๊ฐํ•˜๊ฒŒ๋Š” ์„œ๋น„์Šค์˜ ์žฅ์‹œ๊ฐ„ ์žฅ์• ๋กœ๋„ ์ด์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

 

Alembic

Python ๊ฐœ๋ฐœ ์Šคํƒ์—์„œ alembic์€ ์ด๋Ÿฌํ•œ DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Spring ๊ฐœ๋ฐœ ์Šคํƒ์—์„œ Flyway์™€ ๋™์ผํ•œ ์—ญํ• ์„ ํ•˜๋Š” ๋…€์„์ž…๋‹ˆ๋‹ค. 

 

๋ชจ๋“  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ alembic ์Šคํฌ๋ฆฝํŠธ์— ์˜ํ•ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ๊ฐ ์ธ์Šคํ„ด์Šค์—์„œ๋Š” migration ํ…Œ์ด๋ธ”์ด ์กด์žฌํ•˜๊ณ , ์ด ํ…Œ์ด๋ธ”์„ ํ†ตํ•ด ์–ด๋””๊นŒ์ง€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ๋˜์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๋ฅผ ํ†ตํ•ด ์ค‘๋ณต ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Flyway์™€ ๋‹ค๋ฅธ ์ ์ด ์žˆ๋‹ค๋ฉด Flyway๋Š” ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ ์ •๋ณด๋ฅผ ๊ฐ ํ…Œ์ด๋ธ”์— ๋ชจ๋‘ ๊ธฐ๋กํ•˜๋Š” ๋ฐ˜๋ฉด, alembic์€ ๋งˆ์ง€๋ง‰์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋œ ๋ฒ„์ „์˜ ํ•ด์‹œ๊ฐ’๋งŒ์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

 

 

 

 

Initialization alembic

alembic์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋””ํŽœ๋˜์‹œ๋ฅผ ์„ค์น˜ํ•œ ๋‹ค์Œ ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ตœ์ƒ์œ„์— ์›ํ•˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ ์ด๋ฆ„์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํด๋”๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

$ alembic init migrations

alembic init ๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•ด์„œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ , ์ดˆ๊ธฐํ™”ํ•œ ์Šคํฌ๋ฆฝํŠธ์™€ ํŒŒ์ผ์€ migrations ๋ผ๋Š” ์ƒˆ๋กœ์šด ํด๋”๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

 

๊ฐ ํŒŒ์ผ์— ๋Œ€ํ•ด ๊ฐ„๋žตํžˆ ์„ค๋ช…์„ ๋“œ๋ฆฌ์ž๋ฉด..

 

  • versions

    ์ด ํด๋”์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•  ์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

  • env.py

    ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์‹œ ์‹คํ–‰๋˜๋Š” ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

  • script.py.mako

    ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

  • alembic.ini

    env.py ํŒŒ์ผ์—์„œ Configuration ํŒŒ์ผ๋กœ ์‚ฌ์šฉ๋˜๋Š” alembic ์„ค์ • ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ํŒŒ์ผ์€ env.py์™€ versions ๋‘ ๊ฐ€์ง€๊ฐ€ ๋˜๋ฉฐ env.py ํŒŒ์ผ๋กœ ํ•œ ๋ฒˆ ํ™˜๊ฒฝ์„ ์…‹ํŒ…ํ•˜๊ณ  ๋‚œ ๋’ค์—๋Š” ๊ทธ ๋‹ค์Œ๋ถ€ํ„ฐ๋Š” versions๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

 

 

Set database configuration

alembic.ini ํŒŒ์ผ์„ ์—ด๊ณ , sqlalchemy.url ๋ณ€์ˆ˜์— ์—ฐ๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ค๋‹ˆ๋‹ค.

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = postgressql://postgres:postgres@127.0.0.1/nkmemo

ini ํŒŒ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— Python์—์„œ ์ œ๊ณตํ•˜๋Š” string ๊ด€๋ จ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์˜ค์ง ์ •์  ๋ฌธ์ž์—ด๋งŒ์„ ์ด์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ, ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๊ฒŒ ๋˜๋ฉด ํ™˜๊ฒฝ๋ณ„๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•  ๋•Œ๋งˆ๋‹ค ์ธ์Šคํ„ด์Šค ์ฃผ์†Œ๋ฅผ ๋ฐ”๊ฟ”์ค˜์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์ƒ๊น๋‹ˆ๋‹ค.

 

์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” alembic.ini ํŒŒ์ผ์— ์žˆ๋Š” sqlalchemy.url ๋ณ€์ˆ˜๋ฅผ ์ œ๊ฑฐํ•œ ํ›„ env.py ํŒŒ์ผ์—์„œ sqlalchemy.url์„ ์ˆ˜๋™์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

config = context.config

if not config.get_main_option('sqlalchemy.url'):
    config.set_main_option('sqlalchemy.url', 'postgresql://{username}:{password}@{host}:{port}/{db_name}'.format(
    username='postgres', password='postgres', host='127.0.0.1', port='5432', db_name='nkmemo'
))

env.py ํŒŒ์ผ์—๋Š” alembic์—์„œ ์ œ๊ณตํ•˜๋Š” context๊ฐ€ ์žˆ๊ณ , ์ด context๋ฅผ ํ†ตํ•ด์„œ DB ์ธ์Šคํ„ด์Šค ์ฃผ์†Œ๋ฅผ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. env.py ํŒŒ์ผ์€ Python ์ฝ”๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— Python์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜ ๋“ฑ์„ ์ด์šฉํ•˜์—ฌ ํ™˜๊ฒฝ๋ณ„๋กœ DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

Generate Migration script

๊ทธ๋Ÿผ ์ด์ œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ•˜๋‚˜ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. alembic์—์„œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ ํ…œํ”Œ๋ฆฟ์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ดˆ๊ธฐ ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

$ alembic revision -m "Initialize nkmemo entity.."

alembic revision ๋ช…๋ น์–ด์— m ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์— ๋Œ€ํ•œ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๋‚จ๊ธฐ์–ด ์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์–ด๋–ค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ธ์ง€๋ฅผ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

"""Initialize nkmemo entity..

Revision ID: 906254a28c71
Revises: 
Create Date: 2021-01-16 13:10:10.770502

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '906254a28c71'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    pass


def downgrade():
    pass

 

์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜๋ฉด revision, down_revision์ด๋ผ๋Š” ๋ณ€์ˆ˜๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค. ์ด๋“ค ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ alembic์€ ์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋™์ž‘๋˜์—ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

 

upgrade ํ•จ์ˆ˜๋Š” ์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ ์šฉํ–ˆ์„ ๋•Œ ์‚ฌ์šฉํ•  ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋“ค์–ด๊ฐ€๋ฉฐ, downgrade๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ดํ›„ ์ด๋ฅผ ๋‹ค์‹œ ๋กค๋ฐฑํ•  ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

$ alembic upgrade [revision_hash]
$ alembic downgrade [revision_hash or current_revision +- 1]

alembic upgrade ๋ช…๋ น์–ด์™€ ํ•จ๊ป˜ revision ํ•ด์‰ฌ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ•ด๋‹น hash์— ๋งž๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ์˜ upgrade ํ•จ์ˆ˜๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

 

๋ฐ˜๋Œ€๋กœ downgrade๋Š” ํ˜„์žฌ์˜ revision์—์„œ +- ์ˆซ์ž๋ฅผ ์กฐ์ • ์ž…๋ ฅํ•˜์—ฌ ๋กค๋ฐฑ๊ณผ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ๋ฐ˜๋ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

๊ทธ๋Ÿผ ์ง์ ‘ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์งœ๋ณด๋„๋ก ํ•˜์ฃ . 

"""Initialize nkmemo entity..

Revision ID: 906254a28c71
Revises: 
Create Date: 2021-01-16 13:10:10.770502

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '906254a28c71'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    op.create_table('memos',
                    sa.Column('id', sa.String(length=120), nullable=False),
                    sa.Column('title', sa.String(length=80), nullable=False, index=True),
                    sa.Column('content', sa.Text, nullable=True),
                    sa.Column('is_favorite', sa.Boolean, nullable=False),
                    sa.PrimaryKeyConstraint('id'))


def downgrade():
    op.drop_table('memos')

alembic์˜ op์—์„œ ์ œ๊ณตํ•˜๋Š” create_table, drop_table ๋“ฑ์˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘ ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ , sqlalchemy์˜ Column, String ๋“ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปฌ๋Ÿผ์˜ ์ •๋ณด๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ alembic upgrade head

alembic upgrade์˜ head๋ฅผ ์ด์šฉํ•˜๋ฉด ์ตœ์‹ ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๊นŒ์ง€๋ฅผ ์ „๋ถ€ ์ ์šฉํ•˜๋ฉฐ ์ ์šฉ์ด ๋๋‚ฌ์œผ๋ฉด memos๋ผ๋Š” ํ…Œ์ด๋ธ”์ด ๋งŒ๋“ค์–ด์ ธ ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

Using auto generate

Alembic์—์„œ ์ œ๊ณตํ•˜๋Š” autogenerate ์˜ต์…˜์€ SQLAlchemy์˜ ORM์„ ์ด์šฉํ•˜์—ฌ ์ •์˜ํ•œ Model์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์„ ์ž๋™์œผ๋กœ ํ™•์ธํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋Š” ์ข‹์€ ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜ ๋‹จ์ ์ด ์žˆ๋‹ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ž‘์„ฑ์ด ์‹œ์ž‘๋œ ์‹œ์ ๋ถ€ํ„ฐ autogenerate๋ฅผ ๊พธ์ค€ํžˆ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฉฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์ค‘๊ฐ„์— ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋ณ€๊ฒฝ์ ์„ alembic์„ ํŒŒ์•…ํ•˜์ง€ ๋ชปํ•˜์—ฌ ์˜คํžˆ๋ ค ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํฐ ๊ฒฐ๊ณผ๋ฅผ ์ดˆ๋ž˜ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. 

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata

autogenerate ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € target_metadata ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค. ์ดˆ๊ธฐ๊ฐ’์ด None์œผ๋กœ ์„ค์ •์ด ๋˜์–ด ์žˆ๋Š”๋ฐ, SQLAlchemy๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด SQLAlchemy์—์„œ ์ œ๊ณตํ•˜๋Š” declarative_base ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” metadata๋ฅผ ์‚ฌ์šฉํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

class Memo(Base):
    __tablename__ = 'memos'

    id = Column(String(120), primary_key=True, default=lambda: str(uuid.uuid4()))
    title = Column(String(80), default='No title', nullable=False, index=True)
    content = Column(Text, nullable=True)
    is_favorite = Column(Boolean, nullable=False, default=False)

์ง€๋‚œ๋ฒˆ์— ์ž‘์„ฑํ•œ Memo ๋ชจ๋ธ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ ์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

$ alembic revision --autogenerate -m "Initialize nkmemo entity.."
"""Initialize nkmemo entity..

Revision ID: 62040f1ba614
Revises: 
Create Date: 2021-01-16 13:37:22.969248

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '62040f1ba614'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('memos',
    sa.Column('id', sa.String(length=120), nullable=False),
    sa.Column('title', sa.String(length=80), nullable=False),
    sa.Column('content', sa.Text(), nullable=True),
    sa.Column('is_favorite', sa.Boolean(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_memos_title'), 'memos', ['title'], unique=False)
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f('ix_memos_title'), table_name='memos')
    op.drop_table('memos')
    # ### end Alembic commands ###

๊ทธ๋Ÿฌ๋ฉด ์ด๋ ‡๊ฒŒ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ memos ํ…Œ์ด๋ธ”์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

 

Alembic version management

alembic์ด ์–ด๋–ค์‹์œผ๋กœ ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•˜๋Š”์ง€ ์ž ๊น ๋‘˜๋Ÿฌ๋ณด๋„๋ก ํ• ๊นŒ์š”?

 

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ตœ์ดˆ๋กœ ๋Œ๋ฆฌ๋ฉด ์ด๋ ‡๊ฒŒ ํ…Œ์ด๋ธ”์ด ๋งŒ๋“ค์–ด์ง€๊ณ , ๊ทธ์™€ ํ•จ๊ป˜ alembic_version์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ƒˆ๋กœ์šด ํ…Œ์ด๋ธ”์ด ํ•˜๋‚˜ ๋” ์ƒ์„ฑ์ด ๋˜๋Š”๋ฐ์š”.

 

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

 

๊ทธ๋Ÿฐ๋ฐ, ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋Š” ์Šคํ‚ค๋งˆ๊ณผ ๊ฐ™์€ ๊ณณ์— ๋ฒ„์ „ ๊ด€๋ฆฌ ํ…Œ์ด๋ธ”์„ ๋†“๊ฒŒ ๋˜๋‹ˆ ์ด๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. alembic์˜ ๋ฒ„์ „ ๊ด€๋ฆฌ ํ…Œ์ด๋ธ”์„ ๋‹ค๋ฅธ ์Šคํ‚ค๋งˆ์—์„œ ์šด์˜ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?

 

ํ•ด๋‹น ์ƒํƒœ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด, alembic downgrade๋ฅผ ์ง„ํ–‰ํ•œ ๋’ค, env.py ํŒŒ์ผ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์ง€์ •ํ•˜์—ฌ alembic์˜ ํ…Œ์ด๋ธ”์„ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata,
            include_schema=True,
            version_table_schema='alembic'
        )

        connection.execute('CREATE SCHEMA IF NOT EXISTS {schema}'.format(schema='alembic'))

        with context.begin_transaction():
            context.run_migrations()

์ €๋Š” ์Šคํ‚ค๋งˆ๋ฅผ alembic์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ์„ ๋Œ€๋น„ํ•˜์—ฌ alembic ์Šคํ‚ค๋งˆ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ, alembic ์Šคํ‚ค๋งˆ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ๋„๋ก CREATE SCHEMA IF ์ฟผ๋ฆฌ๋ฅผ ์‚ฝ์ž…ํ•ด์ฃผ๋„๋ก ํ•ฉ์‹œ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด ์ด๋ ‡๊ฒŒ ๊น”๋”ํ•˜๊ฒŒ ์„œ๋น„์Šค ์Šคํ‚ค๋งˆ์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ด€๋ฆฌ ์Šคํ‚ค๋งˆ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด, ์ข€ ๋” ๋ชจ๋ธ์ด ๋ณด๊ธฐ ์‰ฌ์›Œ์ง„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

 

๋งˆ์น˜๋ฉฐ...

์—ฌ๊ธฐ๊นŒ์ง€ alembic๊ณผ SQLAlchemy๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ข€ ๋” ํšจ์œจ์ ์œผ๋กœ ํŽธ๋ฆฌํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์Šต๋‹ˆ๋‹ค. 

 

์ด ๊ธ€์€ ์นดํ…Œ๊ณ ๋ฆฌ ๋ฒ”์ฃผ๊ฐ€ FastAPI๋กœ ์†ํ•ด์ ธ ์žˆ๊ธด ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋„ ์ถฉ๋ถ„ํžˆ ์ด ๋ฐฉ๋ฒ•์„ ์‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ Django๋‚˜ Flask์™€ ๊ฐ™์ด ์ „์šฉ ORM์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•ด์„œ๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , ์ˆœ์ˆ˜ SQLAlchemy ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ด ๊ธ€์„ ๋”ฐ๋ฅด์‹œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

 

๊ฐœ์ธ์ ์œผ๋กœ Flyway๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋ณด๋‹ค ์ง๊ด€์ ์ธ ๋ฉด์ด ์กฐ๊ธˆ ๋–จ์–ด์ง€๋Š” ๋Š๋‚Œ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. Flyway๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์— ์ „๋ถ€ ๋ณด์—ฌ์คŒ์œผ๋กœ์จ ์–ด๋–ค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์‹คํ–‰๋˜์—ˆ๊ณ , ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋Š”์ง€๊ฐ€ ํ•œ ๋ˆˆ์— ๋ณด์ด๋Š” ํŽธ๋ฆฌํ•จ์ด ์žˆ์—ˆ์ง€๋งŒ alembic์€ ๋‹จ์ˆœํžˆ ๋งˆ์ง€๋ง‰์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ๋ฒ„์ „๋งŒ์„ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ผ์ผ์ด ๊ณผ๊ฑฐ์— ์–ด๋–ค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์žˆ์—ˆ๋Š”์ง€๋ฅผ ์ง์ ‘ ์ถ”์ ํ•ด์•ผํ•˜๋Š” ๋ถˆํŽธํ•จ์„ ์ดˆ๋ž˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋ฐ˜๋ฉด์— Flyway๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์‹œ Java ์ฝ”๋“œ์™€ ๋ณ‘ํ–‰์—†์ด ์˜ค์ง SQL ์ฝ”๋“œ๋งŒ์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ๊ณ , ์—ฌ๋Ÿฌ ๊ฐœ์˜ Dialect๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํ•ด๋‹น Dialect๋งˆ๋‹ค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ๋ณ„๋„๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์„ ๊ฐ์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ alembic์€ ์–ด๋Š ์ •๋„ ๋‚œ์ด๋„ ์—†๋Š” ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด์„œ๋Š” ํ†ตํ•ฉ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก Python script๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•ด์ค€๋‹ค๋Š” ์ ์—์„œ ํŽธ๋ฆฌํ•จ์„ ์–ป์—ˆ๋‹ค๋Š” ๊ฒƒ์ด ์ €์˜ ๊ฐœ์ธ์ ์ธ ์˜๊ฒฌ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋ง๋ถ™์—ฌ์„œ, autogenerate ๊ธฐ๋Šฅ์ด ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์ง€๋งŒ ๊ธฐ๋ณธ์ ์ธ ์ปฌ๋Ÿผ ๋ณ€ํ™”๋‚˜ index์˜ ์ถ”๊ฐ€ ์œ ๋ฌด์— ์ •ํ™•ํ•˜๊ฒŒ ํŠธ๋ž˜ํ‚นํ•จ์œผ๋กœ์จ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์— ๋“ค์–ด๊ฐ€๋Š” ๋น„์šฉ์„ ๋งŽ์ด ๋‚ฎ์ถฐ์ฃผ๋Š” ์žฅ์ ์„ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๊ณ , ๊ฐ„๋‹จํ•œ ์ปค๋งจ๋“œ ์‚ฌ์šฉ์œผ๋กœ ํ…œํ”Œ๋ฆฟ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค๋Š” ์ ์€ ๊ฐ๋ช…๊นŠ์—ˆ๋„ค์š”.

 

 

๋ฐ˜์‘ํ˜•
TAGS.

Tistory Comments