ããã§ãæ°ãããããžã§ã¯ããäœæããããšã«ããŸããã ãããŠããã®ãããžã§ã¯ãã¯Webã¢ããªã±ãŒã·ã§ã³ã§ãã åºæ¬çãªãããã¿ã€ããäœæããã®ã«ã©ããããæéãããããŸããïŒ ããã¯ã©ãã»ã©é£ããã§ããïŒ çŸä»£ã®ãŠã§ããµã€ãã¯æåããäœãã§ããã¯ãã§ããïŒ
ãã®èšäºã§ã¯ã次ã®ã¢ãŒããã¯ãã£ãåããåçŽãªWebã¢ããªã±ãŒã·ã§ã³ã®å®åçãªæŠèŠã説æããŸãã
ã«ããŒãããã®ïŒ
- docker-composeã§éçºç°å¢ãèšå®ããŸãã
- Flaskã§ã®ããã¯ãšã³ãã®äœæã
- Expressã§ããã³ããšã³ããäœæããŸãã
- Webpackã䜿çšããŠJSããã«ãããŸãã
- ReactãReduxãããã³ãµãŒããŒåŽã®ã¬ã³ããªã³ã°ã
- RQã䜿çšããã¿ã¹ã¯ãã¥ãŒã
ã¯ããã«
ãã¡ãããéçºã®åã«ããŸãäœãéçºããããæ±ºããå¿
èŠããããŸãïŒ ãã®èšäºã®ã¢ãã«ã¢ããªã±ãŒã·ã§ã³ãšããŠãåå§çãªWikiãšã³ãžã³ãäœæããããšã«ããŸããã Markdownã§ã«ãŒããçºè¡ããŸãã èŠèŽããïŒå°æ¥çã«ã¯ïŒç·šéãæäŸã§ããŸãã ãããã¯ãã¹ãŠããµãŒããŒåŽã¬ã³ããªã³ã°ãåãã1ããŒãžã®ã¢ããªã±ãŒã·ã§ã³ãšããŠé
眮ããŸãïŒããã¯ãå°æ¥ã®ãã©ãã€ãã®ã³ã³ãã³ãã®ã€ã³ããã¯ã¹äœæã«çµ¶å¯Ÿã«å¿
èŠã§ãïŒã
ããã«å¿
èŠãªã³ã³ããŒãã³ããããå°ã詳ããèŠãŠã¿ãŸãããã
- ãå®¢æ§ ããã³ããšã³ãã®äžçã§ã¯éåžžã«äžè¬çãªReact + Reduxãã³ãã«ã§ã1ããŒãžã®ã¢ããªã±ãŒã·ã§ã³ïŒã€ãŸããAJAXã䜿çšããããŒãžé·ç§»ã䜿çšïŒãäœæããŸãããã
- ããã³ããšã³ã ã Reactã¢ããªã±ãŒã·ã§ã³ãã¬ã³ããªã³ã°ãïŒããã¯ãšã³ãã®ãã¹ãŠã®å¿
èŠãªããŒã¿ãéåæã§èŠæ±ããïŒããŠãŒã¶ãŒã«çºè¡ããç°¡åãªExpressãµãŒããŒãäœæããŸãããã
- ããã¯ãšã³ã ã ããžãã¹ããžãã¯ã®ãã¹ã¿ãŒã§ããããã¯ãšã³ãã¯ãå°ããªFlaskã¢ããªã±ãŒã·ã§ã³ã«ãªããŸãã 人æ°ã®ããMongoDBããã¥ã¡ã³ããªããžããªã«ããŒã¿ïŒã«ãŒãïŒãä¿åããã¿ã¹ã¯ãã¥ãŒãšãå°æ¥çã«ã¯ãã£ãã·ã¥ã®ããã«ã Redisã䜿çšããŸãã
- ã¯ãŒã«ãŒ ã éãã¿ã¹ã¯çšã®å¥ã®ã³ã³ãããRQã©ã€ãã©ãªã«ãã£ãŠèµ·åãããŸãã
ã€ã³ãã©ã¹ãã©ã¯ãã£ïŒgit
ãããããããã«ã€ããŠè©±ãããšã¯ã§ããŸããã§ãããããã¡ãããgitãªããžããªã§éçºãè¡ããŸãã
git init git remote add origin git@github.com:Saluev/habr-app-demo.git git commit --allow-empty -m "Initial commit" git push
ïŒããã§ã¯ãããã«
.gitignore
ããå¿
èŠããããŸããïŒ
æçµãã©ããã¯
Githubã§è¡šç€ºã§ããŸãã èšäºã®åã»ã¯ã·ã§ã³ã¯1ã€ã®ã³ãããã«å¯Ÿå¿ããŠããŸãïŒãããéæããããã«å€ãã®ããšãèãçŽããŸããïŒïŒ
ã€ã³ãã©ã¹ãã©ã¯ãã£ïŒdocker-compose
ç°å¢ãã»ããã¢ããããããšããå§ããŸãããã è±å¯ãªã³ã³ããŒãã³ãããããããéåžžã«è«ççãªéçºãœãªã¥ãŒã·ã§ã³ã¯docker-composeã䜿çšããããšã§ãã
docker-compose.yml
ãã¡ã€ã«ã次ã®å
容ã§ãªããžããªã«è¿œå ããŸãã
version: '3' services: mongo: image: "mongo:latest" redis: image: "redis:alpine" backend: build: context: . dockerfile: ./docker/backend/Dockerfile environment: - APP_ENV=dev depends_on: - mongo - redis ports: - "40001:40001" volumes: - .:/code frontend: build: context: . dockerfile: ./docker/frontend/Dockerfile environment: - APP_ENV=dev - APP_BACKEND_URL=backend:40001 - APP_FRONTEND_PORT=40002 depends_on: - backend ports: - "40002:40002" volumes: - ./frontend:/app/src worker: build: context: . dockerfile: ./docker/worker/Dockerfile environment: - APP_ENV=dev depends_on: - mongo - redis volumes: - .:/code
ããã§äœãèµ·ãã£ãŠããããç°¡åã«èŠãŠã¿ãŸãããã
- MongoDBã³ã³ãããŒãšRedisã³ã³ãããŒãäœæãããŸãã
- ããã¯ãšã³ãã®ã³ã³ãããäœæãããŸãïŒä»¥äžã§èª¬æããŸãïŒã ç°å¢å€æ°APP_ENV = devãæž¡ããïŒã©ã®Flaskèšå®ãèªã¿èŸŒãããçè§£ããããã«èŠãŠãããŸãïŒãããŒã40001ãå€éšã§éããŸãïŒãããéããŠããã©ãŠã¶ãŒã¯ã©ã€ã¢ã³ãã¯APIã«ã¢ã¯ã»ã¹ããŸãïŒã
- ããã³ããšã³ãã®ã³ã³ãããäœæãããŸãã ããŸããŸãªç°å¢å€æ°ãããã«ã¹ããŒãããåŸã§äŸ¿å©ã«ãªããããŒã40002ãéããŸãããããWebã¢ããªã±ãŒã·ã§ã³ã®ã¡ã€ã³ããŒãã§ãããã©ãŠã¶ã§httpïŒ// localhostïŒ40002ã«ç§»åããŸãã
- ã¯ãŒã«ãŒã®ã³ã³ãããäœæãããŸãã 圌ã¯å€éšããŒããå¿
èŠãšãããMongoDBãšRedisã§ã¯ã¢ã¯ã»ã¹ã®ã¿ãå¿
èŠã§ãã
ã§ã¯ãdockerfilesãäœæããŸãããã çŸæç¹ã§ã¯ãDocker
ã«é¢ãã åªãã èšäº 㮠翻蚳 ã·ãªãŒãºã Habréã«
æ²èŒãããŠããŸãã詳现ã«ã€ããŠã¯å®å
šã«ã¢ã¯ã»ã¹ã§ããŸãã
ããã¯ãšã³ãããå§ããŸãããã
# docker/backend/Dockerfile FROM python:stretch COPY requirements.txt /tmp/ RUN pip install -r /tmp/requirements.txt ADD . /code WORKDIR /code CMD gunicorn -w 1 -b 0.0.0.0:40001 --worker-class gevent backend.server:app
backend.server
ã¢ãžã¥ãŒã«ã®
app
ãšããååã®äžã«é ããŠãgunicorn Flaskã¢ããªã±ãŒã·ã§ã³ãå®è¡ããŠããããšãããããŸãã
ããã»ã©éèŠã§ã¯ãªã
docker/backend/.dockerignore
ïŒ
.git .idea .logs .pytest_cache frontend tests venv *.pyc *.pyo
ã¯ãŒã«ãŒã¯äžè¬çã«ããã¯ãšã³ãã«äŒŒãŠããŸãããgunicornã®ä»£ããã«éåžžã®ãããã¢ãžã¥ãŒã«ã®èµ·åããããŸãã
# docker/worker/Dockerfile FROM python:stretch COPY requirements.txt /tmp/ RUN pip install -r /tmp/requirements.txt ADD . /code WORKDIR /code CMD python -m worker
worker/__main__.py
ã§ãã¹ãŠã®äœæ¥ã
worker/__main__.py
ãŸãã
.dockerignore
ã¯ãŒã«ãŒã¯ã
.dockerignore
ããã¯ãšã³ãã«å®å
šã«äŒŒãŠããŸãã
æåŸã«ãããã³ããšã³ãã Habréã«ã€ããŠã¯åœŒã«é¢ãããŸã£ãã
å¥ã®èšäºããããŸããã
StackOverflowã®
åºç¯ãªè°è«ãšãGuysãããã¯æ¢ã«2018幎ã§ããããŸã éåžžã®è§£æ±ºçã¯ãããŸãããïŒããšãã粟ç¥ã®ã³ã¡ã³ã
ãã倿ãããšããã¹ãŠãããã»ã©åçŽã§ã¯ãããŸããã ãã®ããŒãžã§ã³ã®dockerãã¡ã€ã«ã«æ±ºããŸããã
# docker/frontend/Dockerfile FROM node:carbon WORKDIR /app # package.json package-lock.json npm install, . COPY frontend/package*.json ./ RUN npm install # , # PATH. ENV PATH /app/node_modules/.bin:$PATH # . ADD frontend /app/src WORKDIR /app/src RUN npm run build CMD npm run start
é·æïŒ
- ãã¹ãŠãæåŸ
éãã«ãã£ãã·ã¥ãããŸãïŒæäžå±€-äŸåé¢ä¿ãæäžéš-ã¢ããªã±ãŒã·ã§ã³ã®ãã«ãïŒ;
docker-compose exec frontend npm install --save newDependency
æ©èœãããªããžããªã®package.json
ã倿ŽããŸãïŒå€ãã®äººã瀺åããããã«ãCOPYã䜿çšããå Žåã¯ããã§ã¯ãããŸããïŒã ãšã«ããã npm install --save newDependency
ã³ã³ããã®å€éšã§å®è¡ããããšã¯æãŸãããããŸãããæ°ããããã±ãŒãžã®ããã€ãã®äŸåé¢ä¿ãæ¢ã«ååšããç°ãªããã©ãããã©ãŒã ã®äžã«æ§ç¯ãããŠããå¯èœæ§ãããããã§ãïŒããšãã°ãäœæ¥äžã®Macbookã®äžã§ã¯ãªããDockerå
ã®ãã®ïŒ ïŒãããã§ããéçºãã·ã³ã«NodeãååšããããšãèŠæ±ããããããŸããã ãã¹ãŠãæ¯é
ãã1ã€ã®DockerïŒ
ããŠããã¡ãã
docker/frontend/.dockerignore
ïŒ
.git .idea .logs .pytest_cache backend worker tools node_modules npm-debug tests venv
ããã§ãã³ã³ãããã¬ãŒã ã®æºåãæŽããå
容ãå
¥åã§ããŸãïŒ
ããã¯ãšã³ãïŒFlask framework
flask
ã
flask-cors
gevent
ã
gevent
ã
gunicorn
ã
requirements.txt
远å ããç°¡åãªFlaskã¢ããªã±ãŒã·ã§ã³ã
backend/server.py
äœæã
requirements.txt
ã
Flaskã«
backend.{env}_settings
ãã¡ã€ã«
backend.{env}_settings
ããèšå®ããã«ã¢ããããããã«æç€ºããŸãã
backend.{env}_settings
ããã¯ããã¹ãŠã®
backend/dev_settings.py
ããã«ïŒå°ãªããšã空ã®ïŒãã¡ã€ã«
backend/dev_settings.py
ãäœæããå¿
èŠãããããšãæå³ããŸãã
ããã§ãããã¯ãšã³ããæ£åŒã«ç«ã¡äžããããšãã§ããŸãïŒ
habr-app-demo$ docker-compose up backend ... backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Starting gunicorn 19.9.0 backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Listening at: http://0.0.0.0:40001 (6) backend_1 | [2019-02-23 10:09:03 +0000] [6] [INFO] Using worker: gevent backend_1 | [2019-02-23 10:09:03 +0000] [9] [INFO] Booting worker with pid: 9
å
ã«é²ã¿ãŸãã
ããã³ããšã³ãïŒExpressãã¬ãŒã ã¯ãŒã¯
ããã±ãŒãžãäœæããããšããå§ããŸãããã ããã³ããšã³ããã©ã«ããŒãäœæãããã®äžã§
npm init
ãå®è¡ãããšãæŽç·ŽãããŠããªãããã€ãã®è³ªåã®åŸã«ã宿ããpackage.jsonãã¹ããªããã§ååŸãããŸãã
{ "name": "habr-app-demo", "version": "0.0.1", "description": "This is an app demo for Habr article.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/Saluev/habr-app-demo.git" }, "author": "Tigran Saluev <tigran@saluev.com>", "license": "MIT", "bugs": { "url": "https://github.com/Saluev/habr-app-demo/issues" }, "homepage": "https://github.com/Saluev/habr-app-demo#readme" }
å°æ¥ãéçºè
ã®ãã·ã³ã«ã¯Node.jsã¯ãŸã£ããå¿
èŠãããŸããïŒDockerã䜿çšããŠ
npm init
ã
npm init
ããã³éå§ã§ããŸããããŸããŸãã§ãïŒã
Dockerfile
npm run build
ããã³
npm run start
ã«èšåããŸãã
Dockerfile
é©åãªã³ãã³ãã远å ããå¿
èŠããããŸãã
build
ã³ãã³ãã¯ãŸã äœãããŸããããããã§ãæçšã§ãã
Expressã®äŸåé¢ä¿ã远å ãã
index.js
ç°¡åãªã¢ããªã±ãŒã·ã§ã³ãäœæã
index.js
ã
ããã§ãããã³ã
docker-compose up frontend
ã
docker-compose up frontend
ãšã³ãã«ãªããŸãïŒ ããã«ã
httpïŒ// localhostïŒ40002ã§ã¯ãå€å
žçãªãHelloãworldãããã§ã«æ«é²ãããŠããã¯ãã§ãã
ããã³ããšã³ãïŒwebpackããã³Reactã¢ããªã±ãŒã·ã§ã³ã§ãã«ã
ä»åºŠã¯ãã¢ããªã±ãŒã·ã§ã³ã§ãã¬ãŒã³ããã¹ã以å€ã®äœããæããšãã§ãã ãã®ã»ã¯ã·ã§ã³ã§ã¯ã
App
æãåçŽãªReactã³ã³ããŒãã³ãã远å ããã¢ã»ã³ããªãæ§æããŸãã
Reactã§ããã°ã©ãã³ã°ãããšãã¯ãæ§ææ§é ã«ãã£ãŠæ¡åŒµãããJavaScriptã®æ¹èšã§ãã
JSXã䜿çšãããšéåžžã«äŸ¿å©ã§ãã
render() { return <MyButton color="blue">{this.props.caption}</MyButton>; }
ãã ããJavaScriptãšã³ãžã³ã¯ãããçè§£ããªããããéåžžããã«ããã§ãŒãºãããã³ããšã³ãã«è¿œå ãããŸãã ç¹å¥ãªJavaScriptã³ã³ãã€ã©ïŒãããããïŒã¯æ§æç³ãsugarãå€å
žçãªJavaScriptã«å€ããã€ã³ããŒããåŠçããçž®å°ããŸãã
2014幎ã apt-cacheæ€çŽ¢javaãããã£ãŠãæãåçŽãªReactã³ã³ããŒãã³ãã¯éåžžã«åçŽã«èŠããŸãã
圌ã¯åã«èª¬åŸåã®ãããã³ã§æšæ¶ã衚瀺ããã ãã§ãã
å°æ¥ã®ã¢ããªã±ãŒã·ã§ã³ã®æå°éã®HTMLãã¬ãŒã ã¯ãŒã¯ãå«ããã¡ã€ã«
frontend/src/template.js
ã远å ããŸãã
ã¯ã©ã€ã¢ã³ããšã³ããªãã€ã³ãã远å ããŸãã
ãã®çŸããããã¹ãŠæ§ç¯ããã«ã¯ã次ã®ãã®ãå¿
èŠã§ãã
webpackã¯JSã®ãã¡ãã·ã§ããã«ãªè¥è
ãã«ããŒã§ãïŒãã ããããã³ããšã³ãã®èšäºã3æéèªãã§ããŸãããããã¡ãã·ã§ã³
ã«ã€ããŠã¯
ããããŸããïŒã
babelã¯JSXã®ãããªãã¹ãŠã®çš®é¡ã®ããŒã·ã§ã³ã®ã³ã³ãã€ã©ã§ãããåæã«ãã¹ãŠã®IEã±ãŒã¹ã®ããªãã£ã«ãããã€ããŒã§ãã
ããã³ããšã³ãã®åã®å埩ããŸã å®è¡ãããŠããå Žåãããªããããªããã°ãªããªãããšã¯ãã¹ãŠã§ã
docker-compose exec frontend npm install --save \ react \ react-dom docker-compose exec frontend npm install --save-dev \ webpack \ webpack-cli \ babel-loader \ @babel/core \ @babel/polyfill \ @babel/preset-env \ @babel/preset-react
æ°ããäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã æ¬¡ã«webpackãæ§æããŸãã
babelãæ©èœãããã«ã¯ã
frontend/.babelrc
ãèšå®ããå¿
èŠããããŸãã
{ "presets": ["@babel/env", "@babel/react"] }
æåŸã«ã
npm run build
ã³ãã³ããæå³ã®ãããã®ã«ããŸãã
// frontend/package.json ... "scripts": { "build": "webpack", "start": "node /app/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, ...
ããã§ãã¯ã©ã€ã¢ã³ãã¯ãããªãã£ã«ã®ãã³ãã«ãšãã®ãã¹ãŠã®äŸåé¢ä¿ãšãšãã«ãbabelãå®è¡ããã³ã³ãã€ã«ããŠãã¢ããªã·ãã¯ãªçž®å°ãã¡ã€ã«
../dist/client.js
ãŸãã Expressã¢ããªã±ãŒã·ã§ã³ã«éçãã¡ã€ã«ãšããŠã¢ããããŒãããæ©èœã远å ããããã©ã«ãã®ã«ãŒãã§HTMLãè¿ãå§ããŸãã
æåïŒ ããã§ã
docker-compose up --build frontend
ãå®è¡ãããšããHelloãworldïŒããšããæ°ããå
æ²¢ã®ããã©ãããŒã衚瀺ãããReact Developer Toolsæ¡åŒµæ©èœïŒ
Chrome ã
Firefox ïŒãã€ã³ã¹ããŒã«ãããŠããå ŽåãReactã³ã³ããŒãã³ãããªãŒããããŸããéçºè
ããŒã«ã§ïŒ

ããã¯ãšã³ãïŒMongoDBã®ããŒã¿
å
ã«é²ã¿ãã¢ããªã±ãŒã·ã§ã³ã«åœãå¹ã蟌ãåã«ããŸãããã¯ãšã³ãã«æ¯ãå¹ã蟌ãŸãªããã°ãªããŸããã Markdownã§ããŒã¯ã¢ããããã«ãŒããä¿åããã€ããã ã£ãããã§ããä»åºŠã¯ãããå®è¡ããŸãã
pythonã«
ã¯MongoDBã®ORMããããŸãããORMã®äœ¿çšã¯æªè³ªã§ãããšèããŠãããé©åãªãœãªã¥ãŒã·ã§ã³ã®ç ç©¶ã¯ããªãã«ä»»ããŸãã 代ããã«ãã«ãŒããšããã«ä»éãã
DAOã®ç°¡åãªã¯ã©ã¹ãäœæããŸãã
ïŒãŸã Pythonã§å泚éã䜿çšããŠããªãå Žåã¯ãå¿
ã
ãããã® èšäºããã§ãã¯ããŠãã ããïŒïŒ
次ã«
pymongo
ãã
Database
ãªããžã§ã¯ãã
pymongo
ãã
CardDAO
ã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£
ãäœæ
pymongo
ïŒããã
pymongo
ã
requirements.txt
ã«è¿œå ããæé
requirements.txt
ïŒïŒ
ããã¯ãšã³ãèšå®ã§Mongaæ§æãç»é²ããæéã ã³ã³ããã«mongo
mongo
ãšããååãä»ããã ããªã®ã§ã
MONGO_HOST = "mongo"
ïŒ
次ã«ã
MongoCardDAO
ãäœæããFlaskã¢ããªã±ãŒã·ã§ã³ã«ã¢ã¯ã»ã¹ã§ããããã«ããå¿
èŠããããŸãã ãªããžã§ã¯ãã®éåžžã«åçŽãªéå±€ïŒèšå®âpymongoã¯ã©ã€ã¢ã³ãâpymongoããŒã¿ããŒã¹â
MongoCardDAO
ïŒãã§ããŸãããã
äŸåé¢ä¿ã®æ³šå
¥ãè¡ãéäžåã®ãã³ã°ã³ã³ããŒãã³ããããã«äœæããŸãããïŒã¯ãŒã«ãŒãšããŒã«ãå®è¡ãããšãã«åã³åœ¹ç«ã¡ãŸãïŒã
Flaskã¢ããªã±ãŒã·ã§ã³ã«æ°ããã«ãŒãã远å ããŠãçºããæ¥œãã¿ãŸãããïŒ
docker-compose up --build backend
åèµ·åããŸãïŒ

ãã£ãš...ããããŸãã«ã ã³ã³ãã³ãã远å ããå¿
èŠããããŸãïŒ toolsãã©ã«ããŒãéããããã«1ã€ã®ãã¹ãã«ãŒãã远å ããã¹ã¯ãªããã远å ããŸãã
docker-compose exec backend python -m tools.add_test_content
ã
docker-compose exec backend python -m tools.add_test_content
ã³ã³ããå
ã®ã³ã³ãã³ãã§mongaãæºãããŸãã

æåïŒ ä»ãããããã³ããšã³ãã§ããããµããŒããããšãã§ãã
ããã³ããšã³ãïŒRedux
次ã«ãã«ãŒã
/card/:id_or_slug
äœæããŸããããã«ãããReactã¢ããªã±ãŒã·ã§ã³ãéããAPIããã«ãŒãããŒã¿ãèªã¿èŸŒãã§ãäœããã®æ¹æ³ã§è¡šç€ºããŸãã ãããŠããã§ãããããæãé£ããéšåãå§ãŸããŸããããã¯ããµãŒããŒãã€ã³ããã¯ã¹ã®äœæã«é©ããã«ãŒãã®ã³ã³ãã³ããå«ãHTMLãããã«æäŸãããããåæã«ãã¢ããªã±ãŒã·ã§ã³ãã«ãŒãéãããã²ãŒããããšãAPIããJSONã®åœ¢åŒã§ãã¹ãŠã®ããŒã¿ãåä¿¡ããããŒãžããªãŒããŒããŒãããªãããã§ã ãããŠããããã¹ãŠ-ã³ããŒã¢ã³ãããŒã¹ããªãïŒ
Reduxã远å ããããšããå§ããŸãããã Reduxã¯ãç¶æ
ãä¿åããããã®JavaScriptã©ã€ãã©ãªã§ãã ããã¯ããŠãŒã¶ãŒã¢ã¯ã·ã§ã³ããã®ä»ã®è峿·±ãã€ãã³ãäžã«ã³ã³ããŒãã³ããå€åããæ°åã®æé»ã®ç¶æ
ã§ã¯ãªãã1ã€ã®éäžç¶æ
ãæã¡ãã¢ã¯ã·ã§ã³ã®éäžã¡ã«ããºã ãéããŠå€æŽãå ãããšããèãæ¹ã§ãã ãããã£ãŠãããã²ãŒã·ã§ã³ã®åææ®µéã§æåã«GIFã®èªã¿èŸŒã¿ããªã³ã«ããæ¬¡ã«AJAXãä»ããŠãªã¯ãšã¹ããè¡ããæåŸã«æåã³ãŒã«ããã¯ã§ããŒãžã®å¿
èŠãªéšåãæŽæ°ããReduxãã©ãã€ã ã§ãã¢ãã¡ãŒã·ã§ã³ä»ãã®GIFã«ã³ã³ãã³ãã倿Žãããã¢ã¯ã·ã§ã³ãéä¿¡ããŸãã³ã³ããŒãã³ãã®1ã€ã以åã®ã³ã³ãã³ããç Žæ£ããŠã¢ãã¡ãŒã·ã§ã³ãé
眮ãããªã¯ãšã¹ããè¡ããæåã³ãŒã«ããã¯ã§å¥ã®ã¢ã¯ã·ã§ã³ãã³ã³ãã³ããããŒãæžã¿ã«å€æŽããéä¿¡ããããã«ãã°ããŒãã«ç¶æ
ã倿ŽããŸãã äžè¬çã«ãä»ãç§ãã¡ã¯èªåã§ãããèŠãã§ãããã
ã³ã³ããã«æ°ããäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããããšããå§ããŸãããã
docker-compose exec frontend npm install --save \ redux \ react-redux \ redux-thunk \ redux-devtools-extension
1ã€ç®ã¯å®éã«ã¯Reduxã2ã€ç®ã¯ReactãšReduxãæšªæããããã®ç¹å¥ãªã©ã€ãã©ãªïŒäº€é
ã®å°éå®¶ãäœæïŒã3ã€ç®ã¯éåžžã«å¿
èŠãªãã®ã§ããããã®å¿
èŠæ§ã¯
READMEã§æ£åœåãããæåŸã«4ã€ç®ã¯
Redux DevToolsãåäœããããã«å¿
èŠãªã©ã€ãã©ãªã§ã
æ¡åŒµå®åçãªReduxã³ãŒãããå§ããŸããããäœãããªãã¬ãã¥ãŒãµãŒãäœæããç¶æ
ãåæåããŸãã
ç§ãã¡ã®ã¯ã©ã€ã¢ã³ãã¯å°ãå€ããã粟ç¥çã«Reduxã䜿çšããæºåãããŠããŸãïŒ
ããã§docker-compose up --build frontendãå®è¡ããŠãäœãç ŽæããŠããªãããšã確èªã§ããRedux DevToolsã«ããªããã£ãç¶æ
ã衚瀺ãããŸããã

ããã³ããšã³ãïŒã«ãŒãããŒãž
SSRã§ããŒãžãäœæããåã«ãSSRãªãã§ããŒãžãäœæããå¿
èŠããããŸãïŒ æåŸã«ãã«ãŒãã«ã¢ã¯ã»ã¹ããããã«ç¬åµçãªAPIã䜿çšããŠãããã³ããšã³ãã®ã«ãŒãããŒãžãäœæããŸãããã
ã€ã³ããªãžã§ã³ã¹ã掻çšããŠãç§ãã¡ã®ç¶æ
ã®æ§é ãåèšèšããæéã§ãã ãã®ãããã¯ã«
ã¯å€ãã®è³æããã
ãŸãã®ã§ãã€ã³ããªãžã§ã³ã¹ãä¹±çšããªãããšããå§ãããŸããã·ã³ãã«ã«çŠç¹ãåœãŠãŸãã ããšãã°ã次ã®ãšããã§ãã
{ "page": { "type": "card", // // type=card: "cardSlug": "...", // "isFetching": false, // API "cardData": {...}, // ( ) // ... }, // ... }
cardDataã®ã³ã³ãã³ããå°éå
·ãšããŠäœ¿çšãããcardãã³ã³ããŒãã³ããååŸããŸãããïŒå®éã«ã¯mongoã®ã«ãŒãã®ã³ã³ãã³ãã§ãïŒã
次ã«ãã«ãŒããå«ãããŒãžå
šäœã®ã³ã³ããŒãã³ããååŸããŸãã 圌ã¯ãAPIããå¿
èŠãªããŒã¿ãååŸãããããã«ãŒãã«è»¢éãã責任ããããŸãã ãããŠãReact-Reduxã®æ¹æ³ã§ããŒã¿ãååŸããŸãã
æåã«ããã¡ã€ã«
frontend/src/redux/actions.js
ãäœæããAPIããã«ãŒãã®ã³ã³ãã³ããæœåºããã¢ã¯ã·ã§ã³ãäœæããŸãïŒãŸã ãªãå ŽåïŒïŒ
export function fetchCardIfNeeded() { return (dispatch, getState) => { let state = getState().page; if (state.cardData === undefined || state.cardData.slug !== state.cardSlug) { return dispatch(fetchCard()); } }; }
å®éã«ãã§ãããè¡ã
fetchCard
ã¢ã¯ã·ã§ã³ã¯ãããå°ãè€éã§ãã
function fetchCard() { return (dispatch, getState) => {
ãããç§ãã¡ã¯äœããããã¢ã¯ã·ã§ã³ãåŸãŸããïŒã¬ãã¥ãŒãµãŒã§ããããµããŒãããå¿
èŠããããŸãã
ïŒåã
ã®ãã£ãŒã«ãã倿ŽããŠãªããžã§ã¯ããè€è£œããããã®æµè¡ã®æ§æã«æ³šæããŠãã ãããïŒãã¹ãŠã®ããžãã¯ãReduxã¢ã¯ã·ã§ã³ã§å®è¡ãããããã«ãªã£ããããã³ã³ããŒãã³ãèªäœCardPage
ã¯æ¯èŒçåçŽã«èŠããŸãã
ã«ãŒãAppã³ã³ããŒãã³ãã«åçŽãªpage.typeåŠçã远å ããŸãã
ãããŠä»ãæåŸã®ãã€ã³ã-åæåããå¿
èŠãããpage.type
ãšpage.cardSlug
URLã«ãã£ãŠç°ãªããŸãããããããã®èšäºã«ã¯ãŸã å€ãã®ã»ã¯ã·ã§ã³ããããŸãããçŸåšãé«å質ã®ãœãªã¥ãŒã·ã§ã³ãäœæããããšã¯ã§ããŸããããšãããããã«ã«ããŠã¿ãŸããããããã¯ãŸã£ããã°ãã§ããããšãã°ãã¢ããªã±ãŒã·ã§ã³ãåæåãããšãã«å®æçã«ïŒ
ä»ãç§ãã¡ã¯ã®å©ããåããŠãããã³ããšã³ããåæ§ç¯ããããšãã§ããŸãdocker-compose up --build frontend
ç§ãã¡ã®ã«ãŒããæ¥œããããã«ãhelloworld
...
ã ãããã¡ãã£ãšåŸ
ã£ãŠãã ãã...ããã©ãç§ãã¡ã®ã³ã³ãã³ãããããŸããïŒãããMarkdownãè§£æããã®ãå¿ããŸããïŒåŽåè
ïŒRQ
Markdownãè§£æããæœåšçã«ç¡å¶éã®ãµã€ãºã®ã«ãŒãã®HTMLãçæããããšã¯ãå
žåçãªãéããã¿ã¹ã¯ã§ãã倿Žãä¿åããªããããã¯ãšã³ãã§çŽæ¥è§£æ±ºããã®ã§ã¯ãªããéåžžãå¥ã
ã®äœæ¥ãã·ã³ã§ãã¥ãŒã«å
¥ããŠå®è¡ããŸããã¿ã¹ã¯ãã¥ãŒã«ã¯å€ãã®ãªãŒãã³ãœãŒã¹å®è£
ããããŸããRedisãšã·ã³ãã«ãªã©ã€ãã©ãªRQïŒRedis QueueïŒã䜿çšããŸããRQïŒRedis QueueïŒã¯ãã¿ã¹ã¯ãã©ã¡ãŒã¿ãŒãpickle圢åŒã§éä¿¡ããåŠçã®ããã®çæããã»ã¹ãç·šæããŸããèšå®ãšé
ç·ã«å¿ããŠå€§æ ¹ã远å ããæéïŒ
ã¯ãŒã«ãŒã®å®åã³ãŒãã®ãããã
è§£æèªäœã«ã€ããŠã¯ãmistuneã©ã€ãã©ãªãæ¥ç¶ããç°¡åãªé¢æ°ãèšè¿°ããŸãã
è«ççã«ïŒCardDAO
ã«ãŒãã®ãœãŒã¹ã³ãŒããååŸããçµæãä¿åããå¿
èŠããããŸãããã ããå€éšã¹ãã¬ãŒãžãžã®æ¥ç¶ãå«ããªããžã§ã¯ãã¯pickleãä»ããŠã·ãªã¢ã«åã§ããŸãããã€ãŸãããã®ã¿ã¹ã¯ãããã«ååŸããŠRQã®ãã¥ãŒã«å
¥ããããšã¯ã§ããŸãããè¯ãæ¹æ³ã§ã¯Wiring
ãåŽã«ã¯ãŒã«ãŒãäœæããããããçš®é¡ã®ã¯ãŒã«ãŒãã¹ããŒããå¿
èŠããããŸã...ããããã£ãŠã¿ãŸãããïŒ
ãžã§ãã®ã¯ã©ã¹ã宣èšãããã¹ãŠã®åé¡ã§é
ç·ã远å ã®kwargsåŒæ°ãšããŠã¹ããŒããŸãããïŒã¿ã¹ã¯ãåŠçãããåã«RQå
ã§çºçãããã©ãŒã¯ã®åã«äžéšã®ã¯ã©ã€ã¢ã³ããäœæã§ããªããããæ¯åæ°ããé
ç·ãäœæããããšã«æ³šæããŠãã ãããïŒé
ç·ããå¿
èŠãªãã®ã ããååŸãããã³ã¬ãŒã¿ãäœæããŸãããã
ã¿ã¹ã¯ã«ãã³ã¬ãŒã¿ã远å ããŠã人çãæ¥œãã¿ãŸãããïŒ import mistune from backend.storage.card import CardDAO from backend.tasks.task import task @task def parse_card_markup(card_dao: CardDAO, card_id: str): card = card_dao.get_by_id(card_id) card.html = _parse_markdown(card.markdown) card_dao.update(card) _parse_markdown = mistune.Markdown(escape=True, hard_wrap=False)
人çãæ¥œããïŒããŒããç§ã¯èšãããã£ããåŽåè
ãå®è¡ããŸãïŒ $ docker-compose up worker ... Creating habr-app-demo_worker_1 ... done Attaching to habr-app-demo_worker_1 worker_1 | 17:21:03 RQ worker 'rq:worker:49a25686acc34cdfa322feb88a780f00' started, version 0.13.0 worker_1 | 17:21:03 *** Listening on tasks... worker_1 | 17:21:03 Cleaning registries for queue: tasks
III ...圌ã¯äœãããŸããïŒãã¡ãããåäžã®ã¿ã¹ã¯ãèšå®ããªãã£ãããã§ãïŒãã¹ãã«ãŒããäœæããããŒã«ãæžãçŽããŠã次ã®ããã«ããŸããaïŒã«ãŒããæ¢ã«äœæãããŠããå ŽåïŒãã®å Žåã®ããã«ïŒèœã¡ãªããbïŒmarqdownã®è§£æã«ã¿ã¹ã¯ã眮ããŸãã
ããŒã«ã¯ããã¯ãšã³ãã ãã§ãªããã¯ãŒã«ãŒã§ãå®è¡ã§ããããã«ãªããŸãããååãšããŠãä»ã¯æ°ã«ããŸãããç§ãã¡ã¯ãããèµ·åãdocker-compose exec worker python -m tools.add_test_content
ãã¿ãŒããã«ã®é£ã®ã¿ãã«å¥è·¡ãèŠããŸã-åŽåè
ã¯äœããããŸããïŒ worker_1 | 17:34:26 tasks: backend.tasks.parse.parse_card_markup(card_id='5c715dd1e201ce000c6a89fa') (613b53b1-726b-47a4-9c7b-97cad26da1a5) worker_1 | 17:34:27 tasks: Job OK (613b53b1-726b-47a4-9c7b-97cad26da1a5) worker_1 | 17:34:27 Result is kept for 500 seconds
ããã¯ãšã³ãã§ã³ã³ãããåæ§ç¯ããåŸããã©ãŠã¶ã§ã«ãŒãã®å
容ãæçµçã«ç¢ºèªã§ããŸãã
ããã³ããšã³ãããã²ãŒã·ã§ã³
SSRã«é²ãåã«ãReactã®å€§éšããå°ãæå³ã®ãããã®ã«ããåäžããŒãžã®ã¢ããªã±ãŒã·ã§ã³ãæ¬åœã«åäžããŒãžã«ããå¿
èŠããããŸããããŒã«ãæŽæ°ããŠãçžäºã«ãªã³ã¯ãã2ã€ã®ã«ãŒãïŒ1ã€ã§ã¯ãªãã2ã€ãMOMãç§ã¯BIG DATE DEVELOPERïŒïŒãäœæããŸãããããã®åŸããããã®éã®ããã²ãŒã·ã§ã³ãåŠçããŸããããã§ããªã³ã¯ããã©ã£ãŠããã°ãããã¢ããªã±ãŒã·ã§ã³ãåèµ·åãããã³ã«ã©ããªãããèããããšãã§ããŸãã ãããŠïŒ
æåã«ããªã³ã¯ã®ã¯ãªãã¯ã«ãã³ãã©ãŒãé
眮ããŸãããªã³ã¯ä»ãã®HTMLã¯ããã¯ãšã³ãããã®ãã®ã§ãããã¢ããªã±ãŒã·ã§ã³ã¯Reactã䜿çšããŠãããããReactåºæã®æ³šæãå°ãå¿
èŠã§ãã
ã³ã³ããŒãã³ãCardPage
ã«ã«ãŒããããŒããããã¹ãŠã®ããžãã¯ã¯ãã¢ã¯ã·ã§ã³èªäœïŒé©ãã¹ãããšã§ãïŒïŒã§ãããããã¢ã¯ã·ã§ã³ãå®è¡ããå¿
èŠã¯ãããŸããã export function navigate(link) { return { type: NAVIGATE, path: link.pathname } }
ãã®å Žåã«æããªã¬ãã¥ãŒãµãŒã远å ããŸãã
ã¢ããªã±ãŒã·ã§ã³ã®ç¶æ
ã倿Žãããå¯èœCardPage
æ§ãcomponentDidUpdate
ãããããæ¢ã«è¿œå ããã¡ãœãããšåãã¡ãœããã远å ããå¿
èŠãããcomponentWillMount
ãŸããçŸåšãããããã£CardPage
ïŒcardSlug
ããã²ãŒã·ã§ã³äžã®ããããã£ãªã©ïŒãæŽæ°ããåŸãããã¯ãšã³ãããã®ã«ãŒãã®ã³ã³ãã³ããèŠæ±ãããŸãïŒcomponentWillMount
ã³ã³ããŒãã³ãã®åæåæã«ã®ã¿ãããè¡ãããŸãïŒãããŠãdocker-compose up --build frontend
ããã²ãŒã·ã§ã³ãæ©èœããŸããïŒ
æ³šææ·±ãèªè
ã¯ãã«ãŒãéãç§»åããŠãããŒãžã®URLã倿Žãããªãããšã«æ°ä»ãã§ããã-ã¹ã¯ãªãŒã³ã·ã§ããã§ããããã¢ã«ãŒãã®ã¢ãã¬ã¹ã«ããäžçã«ãŒãHelloã衚瀺ãããŸãããããã£ãŠãååŸããã²ãŒã·ã§ã³ãèœã¡ãŸããããããä¿®æ£ããããã«ãããã«æŽå²ãæã€ããã€ãã®é»éè¡ã远å ããŸãããïŒããªããã§ããæãç°¡åãªããšã¯ãã¢ã¯ã·ã§ã³ã«è¿œå ããããšã§ãnavigate
ææŠhistory.pushState
ã export function navigate(link) { history.pushState(null, "", link.href); return { type: NAVIGATE, path: link.pathname } }
ããã§ããªã³ã¯ãã¯ãªãã¯ãããšããã©ãŠã¶ãŒã®ã¢ãã¬ã¹ããŒã®URLãå®éã«å€æŽãããŸãããã ããæ»ããã¿ã³ã¯å£ããŸãïŒåäœãããã«ã¯ãpopstate
ãªããžã§ã¯ãã®ã€ãã³ãããªãã¹ã³ããå¿
èŠããããŸãwindow
ãããã«ããã®ã€ãã³ãã§ãåæ¹ããã³åŸæ¹ïŒã€ãŸããdispatch(navigate(...))
ïŒã«ããã²ãŒã·ã§ã³ãè¡ãããå Žånavigate
ãç¹å¥ãªãdo not pushState
ããã©ã°ã颿°ã«è¿œå ããå¿
èŠããããŸãïŒããããªããšããã¹ãŠãããã«å£ããŸãïŒïŒãããã«ããç§ãã¡ã®ãç¶æ
ãåºå¥pushState
ããããã«ãã¡ã¿ããŒã¿ãä¿åããæ©èœã䜿çšããå¿
èŠããããŸããããããã®éæ³ãšãããã°ããããŸãã®ã§ãããã«ã³ãŒããèŠãŠã¿ãŸãããïŒã¢ããªã¯æ¬¡ã®ããã«ãªããŸãã
ãããŠãããã«ããã²ãŒãã¢ã¯ã·ã§ã³ããããŸãã
ããã§ã¹ããŒãªãŒã¯æ©èœããŸããããŠãæåŸã®ã¿ããïŒã¢ã¯ã·ã§ã³navigate
ãã§ããã®ã§ãã¯ã©ã€ã¢ã³ãã§åæç¶æ
ãèšç®ããäœåãªã³ãŒããæŸæ£ããªãã®ã¯ãªãã§ããïŒçŸåšã®å Žæã«ããã²ãŒãããã ãã§ãã
ã³ããŒããŒã¹ããç Žå£ãããŸããïŒããã³ããšã³ãïŒãµãŒããŒåŽã®ã¬ã³ããªã³ã°
ç§ãã¡ã®ã¡ã€ã³ãããïŒSEOãã¬ã³ããªãŒïŒã®æéã§ããæ€çŽ¢ãšã³ãžã³ãReactã³ã³ããŒãã³ãã§å®å
šã«åçã«äœæãããã³ã³ãã³ããã€ã³ããã¯ã¹åããã«ã¯ãReactã¬ã³ããªã³ã°ã®çµæãæäŸãããã®çµæãåã³ã€ã³ã¿ã©ã¯ãã£ãã«ããæ¹æ³ãåŠç¿ããå¿
èŠããããŸããäžè¬çãªã¹ããŒã ã¯åçŽã§ããæåïŒReactã³ã³ããŒãã³ãã«ãã£ãŠçæãããHTMLãHTMLãã³ãã¬ãŒãã«æ¿å
¥ããå¿
èŠããããŸãApp
ããã®HTMLã¯ãæ€çŽ¢ãšã³ãžã³ïŒããã³JSããªãã«ãªã£ãŠãããã©ãŠã¶ãŒïŒã«è¡šç€ºãããŸãã 2çªç®ïŒãã®HTMLãã¬ã³ããªã³ã°ãããç¶æ
ãã³ã<script>
ãã©ããã«ïŒããšãã°ããªããžã§ã¯ãwindow
ïŒä¿åãããã³ãã¬ãŒãã«ã¿ã°ã远å ããŸãããã®åŸãããã«ãã®ç¶æ
ã§ã¯ã©ã€ã¢ã³ãåŽã¢ããªã±ãŒã·ã§ã³ãåæåããå¿
èŠãªãã®ã衚瀺ã§ããŸãïŒãã€ãã¬ãŒãã䜿çšããããšãã§ããŸãïŒã¢ããªã±ãŒã·ã§ã³ã®DOMããªãŒãåäœæããªãããã«ãçæãããHTMLã«ïŒãã¬ã³ããªã³ã°ãããHTMLãšæçµç¶æ
ãè¿ã颿°ãæžãããšããå§ããŸãããã
äžèšã§èª¬æããæ°ããåŒæ°ãšããžãã¯ããã³ãã¬ãŒãã«è¿œå ããŸãã
ExpressãµãŒããŒã¯ããå°ãè€éã«ãªããŸãã
ããããã¯ã©ã€ã¢ã³ãã¯ç°¡åã§ãã
次ã«ããhistory is not definedããªã©ã®ã¯ãã¹ãã©ãããã©ãŒã ãšã©ãŒãã¯ãªãŒã³ã¢ããããå¿
èŠããããŸãããããè¡ãã«ã¯ãã®ã©ããã«åçŽãªïŒãããŸã§ã®ïŒé¢æ°ã远å ãutility.js
ãŸãã
ãããããããã«ã¯æã£ãŠããªãäžå®æ°ã®å®æçãªå€æŽããããŸãïŒãããããããã¯å¯Ÿå¿ããcommitã§èŠã€ããããšãã§ããŸãïŒããã®çµæãReactã¢ããªã±ãŒã·ã§ã³ã¯ãã©ãŠã¶ãŒãšãµãŒããŒã®äž¡æ¹ã§ã¬ã³ããªã³ã°ã§ããããã«ãªããŸããããŸãããïŒ
ãããã圌ããèšãããã«ã1ã€ã®èŠåããããŸã...
ããŒãã£ã³ã°ïŒ Googleãè¶
ã¯ãŒã«ãªãã¡ãã·ã§ã³ãµãŒãã¹ã§èŠãŠããã®ã¯LOADINGïŒïŒãŸããããã¯ç§ãã¡ã®éåææ§ã®ãã¹ãŠãç§ãã¡ã«å察ããããã§ããæ¬¡ã«ãã«ãŒãã®ã³ã³ãã³ããå«ãããã¯ãšã³ãããã®å¿çã¯ãReactã¢ããªã±ãŒã·ã§ã³ãæååã«ã¬ã³ããªã³ã°ããŠã¯ã©ã€ã¢ã³ãã«éä¿¡ããåã«åŸ
æ©ããå¿
èŠãããããšããµãŒããŒã«çè§£ãããæ¹æ³ãå¿
èŠã§ãããããŠããã®æ¹æ³ã¯ããªãäžè¬çã§ããããšãæãŸãããå€ãã®è§£æ±ºçããããŸãã 1ã€ã®ã¢ãããŒãã¯ãã©ã®ãã¹ã«å¯ŸããŠã©ã®ããŒã¿ãä¿è·ããããå¥ã®ãã¡ã€ã«ã«èšè¿°ããã¢ããªã±ãŒã·ã§ã³ãã¬ã³ããªã³ã°ããåã«ãããè¡ãããšã§ãïŒèšäºïŒããã®ãœãªã¥ãŒã·ã§ã³ã«ã¯å€ãã®å©ç¹ããããŸããããã¯åçŽã§ãæç€ºçã§ãããæ©èœããŸããå®éšãšããŠïŒå
ã®ã³ã³ãã³ãã¯å°ãªããšãèšäºã®ã©ããã«ããã¯ãã§ãïŒïŒå¥ã®ã¹ããŒã ãææ¡ããŸããéåæåŠçãå®è¡ãããã³ã«ãåŸ
æ©ããå¿
èŠããããŸããé©åãªãããã¹ïŒããšãã°ããã§ãããè¿ããããã¹ïŒãç¶æ
ã®ã©ããã«è¿œå ããŸãããã®ããããã¹ãŠãããŠã³ããŒãããããã©ãããåžžã«ç¢ºèªã§ããå ŽæããããŸãã2ã€ã®æ°ããã¢ã¯ã·ã§ã³ã远å ããŸãã
æåã¯ãã§ããã®èµ·åæã«åŒã³åºããã2çªç®ã¯ãã§ããã®æåŸã«åŒã³åºãã.then()
ãŸããæ¬¡ã«ããªãã¥ãŒãµãŒã«åŠçã远å ããŸãã
次ã«ãã¢ã¯ã·ã§ã³ãæ¹åããŸãfetchCard
ã
initialState
空ã®é
åã«ãããã¹ã远å ãããµãŒããŒãããããã¹ãŠãåŸ
ã€ããã«ããããšã¯æ®ã£ãŠããŸãïŒã¬ã³ããªã³ã°é¢æ°ã¯éåæã«ãªããæ¬¡ã®åœ¢åŒãåããŸãã
ååŸãããrender
éåææ§ã«ãããèŠæ±ãã³ãã©ãŒãå°ãè€éã«ãªããŸãã
ã»ãïŒ
ãããã«
ã芧ã®ãšããããã€ãã¯ã¢ããªã±ãŒã·ã§ã³ã®äœæã¯ããã»ã©ç°¡åã§ã¯ãããŸãããããããããã»ã©é£ãããããŸããïŒæçµçãªã¢ããªã±ãŒã·ã§ã³ã¯Githubã®ãªããžããªã«ãããçè«çã«ã¯Dockerãå®è¡ããã ãã§ååã§ããèšäºãéèŠãããå Žåããã®ãªããžããªã¯ç Žæ£ãããŸããïŒå¿
èŠãªä»ã®ç¥èããäœãã調ã¹ãããšãã§ããŸãã- ãã®ã³ã°ãã¢ãã¿ãªã³ã°ãè² è·ãã¹ãã
- ãã¹ããCIãCDã
- èªèšŒãå
šææ€çŽ¢ãªã©ã®åªããæ©èœã
- å®çšŒåç°å¢ã®ã»ããã¢ãããšéçºã
ãæž
èŽããããšãããããŸããïŒ