äžç·ã«èããŠã¿ãŸãããã ããã€ãã®èšäºã§ãç§ã1幎以äžã«ããã£ãŠèªåã®ã¡ãŒã«ãã¥ãŒã¹ã¬ã¿ãŒãµãŒãã¹ãã©ã®ããã«è¡ã£ãŠããããã©ã®ãããªæèšãèªåã®ããã«åŠãã ãããããŠããããã¹ãŠã§æ¬¡ã«äœãããã€ãããã«ã€ããŠã話ããŸãã
èšäºãåé¡ã®æè¡é¢ã®ã¿ãèæ ®ããŠããããšãçŽã¡ã«äºçŽããŠãã ããã
æåã®éšåã¯ããã§èªãããšãã§ããŸã ã
èªåã«ã€ããŠç°¡åã«
ç§ã¯5幎éPythonã§æžããŠããŸããäž»ã«PostgreSQLã®Djangoã䜿çšããŠããŸããjQuery+ KnockoutJSã¬ãã«ã§JavaScriptãæºåã§ããŸããReactã§æžãããšããããŸãã 空ãæéã«ã¯ãUpWorkãšèªåã®ã€ã³ã¿ãŒããããããžã§ã¯ãã§ããªãŒã©ã³ã¹ã®ä»äºãããŸããããã®ãã¡ã®1ã€ã«ã€ããŠã¯ä»ãã話ããŸãã ç§ã¯ãã®ãããžã§ã¯ãã«çŽ1幎åæºãã£ãŠããŸãã
ãã®èšäºã®ç®çã¯äœã§ããïŒ
æåã®éšåã§ã¯ã補åïŒPythonãDjangoãPostgreSQLïŒã®äž»ãªæè¡ãšããŠäœ¿çšããæè¡ãé»åã¡ãŒã«è¿œè·¡ã®ä»çµã¿ãããã³ãµãŒãã¹ã«é¢ããçµ±èšæ å ±ã«ã€ããŠç°¡åã«èª¬æããŸããã
ãã®ããŒãã§ã¯ã次ã®ããšãäŒããŸãã
1.éåæã¿ã¹ã¯ã䜿çšããŠãŠãŒã¶ãŒãèŠçããå®ãæ¹æ³ã Celeryã䜿çšããŠè€æ°ã®ã¿ã¹ã¯ãåæã«å®è¡ããããšãæ€èšããŠãã ããã
2.ãªãœãŒã¹ãéåžžã«éãããŠãããµãŒããŒã§ãæ°çŸäžã®ã¬ã³ãŒããå«ãããŒãã«ã䜿çšããŠäœæ¥ãç·šæããæ¹æ³ã éæ£èŠåãããããŒãã«ãé©åãªå Žåãããçç±ã説æããŸãã
3.ã¢ããªã·ãã¯Djangoãããžã§ã¯ãã®æ©èœã®äžéšãåå¥ã®ãã€ã¯ããµãŒãã¹ã«ç§»è¡ããããã«äœ¿çšãããã¯ãããžãŒãæå°éã®æéã³ã¹ãã§è¿ éã«ç®¡çããæ¹æ³ã
ããã§ã¯å§ããŸãããã
Pythonãããžã§ã¯ãã®éåæã¿ã¹ã¯ããŸãã¯ãŠãŒã¶ãŒãåŸ ããªãçç±
é ããæ©ãããç¹ã«åæã¢ãŒãã§ãŠãŒã¶ãŒãã©ãŠã¶ãŒããã®èŠæ±ãåŠçããWebããŒã¹ã®ã¢ããªã±ãŒã·ã§ã³ã«ã€ããŠè©±ããŠããå Žåãæé·ãããããžã§ã¯ãã¯ãã¹ãŠããã©ãŒãã³ã¹ã®åé¡ã«çŽé¢ããŸãã
æãç°¡åãªäŸ
pythonãããžã§ã¯ãã«å žåçãªãã¯ãããžãŒã¹ã¿ãã¯ã䜿çšãããšããŸãããïŒuwsgiã®æ°ãããã«ããnginxãWebãµãŒããŒ+ pythonã¢ããªã±ãŒã·ã§ã³ã Nginxã¯éçãã¡ã€ã«ãæäŸããã«ãŒããuwsgiã«è»¢éããŸãã uwsgiæ§æã§ã¯ãN人ã®ã¯ãŒã«ãŒãããŸãã
ãããã¯ãã¹ãŠã倧éã®ãéãããªã¯ãšã¹ãïŒå€§ããªãã¡ã€ã«ã®èªã¿èŸŒã¿ãªã©ïŒã«ééãããŸã§ã¹ã ãŒãºã«æ©èœããŸããããã«ã¯ãharakiri uwsgiã«è¿ãããŸãã¯ããããé·ãåŠçæéãå¿ èŠã«ãªããŸãã ãã®åŸãuwsgiã¯ãŒã«ãŒã¯ãŠãŒã¶ãŒããã®ãªã¯ãšã¹ããåŠçããæéããªããªãããŠãŒã¶ãŒã¯nginx 502ãããšã©ãŒãåãåããããµãŒãã¹ãã«é¢ããã³ã¡ã³ããèªããŸãã
ãã¡ããããã«ããŒããasyncio + aiohttpãªã©ã®ãœãªã¥ãŒã·ã§ã³ã«é Œãããšãã§ããŸãããããã¯å°ãç°ãªããã®ã§ããæ¢ã«åæDjangoã¢ããªã±ãŒã·ã§ã³ããããæžãæããç Žæ£ããã®ã¯éåžžã«æ®å¿µã§ãã
奜å¥å¿readerçãªèªè ã¯ãnginxã«è€æ°ã®ã¢ããã¹ããªãŒã ã䜿çšããããšã§Djangoãšaiohttpãçµã¿åãããããšãã§ãããšèšãã§ãããããä»ã¯ãã£ãšæšæºçãªã¢ãããŒããæ€èšããããšæããŸãã
çŸåšãåæPythonãããžã§ã¯ãã®æãæšæºçãªã¢ãããŒãã¯ãã©ã€ãã©ãªã䜿çšããŠCeleryéåæã¿ã¹ã¯ãåŠçããããšã§ãã
以äžã®ãã¹ãŠã®æ å ±ã¯ãããŒãžã§ã³3.xã§æå¹ã§ãã ç§ã¯ããã䜿çšããŸãã Celeryã®ããŒãžã§ã³4ã§ã¯ãå€ããå€æŽãããŸããã
ãã®ã©ã€ãã©ãªã«ãŸã æ £ããŠããªãèªè ã®ããã«ã以äžã®æ¹æ³ãç¥ã£ãŠãããšããç°¡åãªã³ã¡ã³ãïŒ
1. Celeryãéåæã«å®è¡ããïŒãŸãã¯beatã䜿çšããå¿ èŠããããšãã«å®è¡ããããã«å®è¡ããïŒã¿ã¹ã¯ïŒéåžžã®æ©èœïŒãäœæã§ããŸãã
ã·ã¹ãã é»åã¡ãŒã«ãéä¿¡ããåçŽãªã¿ã¹ã¯ã®äŸïŒ
from celery import current_app ... @current_app.task() def send_service_message(subject, recipients, template, html_template, context): html_mail( subject=subject, template=template, html_template=html_template, recipients=recipients, context=context )
ãã®ã¿ã¹ã¯ã®èª²é¡ã¯æ¬¡ã®ãšããã§ãã
from app.message.tasks import send_service_message ... send_service_message.delay( _(u'%s: ' % subscriber.list.project.domain), [user.email], '', 'site/subscriber/email/subscription_notification.html', context )
2. Djangoã䜿çšãããšããããã®ã¿ã¹ã¯ã管çããããã®éåžžã«äŸ¿å©ãªã€ã³ã¿ãŒãã§ã€ã¹ãåŸãããŸããããã«ãããããšãã°ãå®è¡æã®åã¿ã¹ã¯ã®èµ·åãã©ã¡ãŒã¿ãŒãå®çŸ©ã§ããŸãã
3.ãã®ã·ã¹ãã å šäœã¯ãã»ãšãã©ã®å ŽåãRabbitMQïŒã¡ãã»ãŒãžãããŒã«ãŒãšããŠäœ¿çšïŒã«åºã¥ããŠå®è¡ãããã»ãšãã©ã®å Žåãsupervisordã䜿çšããŠå¶åŸ¡ãããŸãã ãŸããããŒã¿ããŒã¹ã¯ãããŒã«ãŒïŒæãé ããªãã·ã§ã³ã§ãããéçºã«ã¯é©ããŠããŸãïŒãRedisïŒãããã³ã«ã®ç¶æãéåžžã«é£ãããããCeleryã®äœæè ã¯äœ¿çšãæšå¥šããŸããïŒãAmazon SQSãšããŠäœ¿çšã§ããŸãã
ç§ã®ãããžã§ã¯ãã§ã®äœ¿çšäŸ
ç§ã®ãããžã§ã¯ãã§ã¯ããŠãŒã¶ãŒã«ã¡ãŒã«ãã¥ãŒã¹ã¬ã¿ãŒãšãã©ã³ã¶ã¯ã·ã§ã³ã¡ãŒã«ãµãŒãã¹ãæäŸããŠããŸãã
ãããã£ãŠãæœåšçã«2çš®é¡ã®éåæã¿ã¹ã¯ããããŸãã
1.é·ãã¿ã¹ã¯ã éµäŸ¿ç©ã¯ãããã20ãã25000éã®æçŽã§ãæçŽã®éä¿¡ã«å¿ èŠãªã¿ã€ã ã¢ãŠãã®ããã«6-7æé以å ã«å®è¡ã§ããŸãã
2.çãã¿ã¹ã¯ã ãŠãŒã¶ãŒããµã€ãã«ç»é²ãããã®ããã¯ãšã³ããç»é²ç¢ºèªãèšèŒããã¡ãŒã«ãç§ã®ãµãŒãã¹ã®APIã«éä¿¡ãããªã¯ãšã¹ããéä¿¡ããŸãããæ°ç§åŸïŒãŸãã¯ãã以äžã®éãïŒã«ããŠãŒã¶ãŒã¯åä¿¡ãã¬ã€ã«ãã®æçŽãèŠãã¯ãã§ãã
1ã€ã®ãã¥ãŒå ã§ãã¹ãŠã®ã¿ã¹ã¯ã®èšç»ãšå®è¡ãå¿ãããšïŒã€ãŸããããã©ã«ãã§ïŒããã§ã«å®è¡ãããŠããé·ãã¿ã¹ã¯ãæ¢ã«ããå Žåããã¹ãŠã®çãã¿ã¹ã¯ã¯ãã®å®äºãåŸ ã¡ãŸãã
6ã7æéåŸã«ãã©ã³ã¶ã¯ã·ã§ã³ã¬ã¿ãŒãåãåãããšã¯ãªãã·ã§ã³ã§ã¯ãªãããšã¯æããã§ãããããã¿ã¹ã¯ã®äžŠååŠçã«è€æ°ã®ãã¥ãŒã䜿çšããããšã«ãªããŸãã
ãããè¡ãã«ã¯ããããžã§ã¯ãã§ãã¥ãŒèªäœãæ§æããŸãã
# settings.py ... CELERY_QUEUES = ( Queue('celery', Exchange('celery'), routing_key='celery'), Queue('bulk', Exchange('bulk'), routing_key='bulk'), Queue('transactional', Exchange('transactional'), routing_key='transactional'), ) CELERY_ROUTES = { 'app.campaign.tasks.start_campaign': { 'queue': 'bulk' }, 'app.message.tasks.send_service_message': { 'queue': 'transactional' }, 'app.message.api.tasks.send_message': { 'queue': 'transactional' } }
ç§ã®ãããžã§ã¯ãã§ã¯ã3ã€ã®ãã¥ãŒã䜿çšããŸãã
1.ãã©ã³ã¶ã¯ã·ã§ã³-ãã©ã³ã¶ã¯ã·ã§ã³ã¬ã¿ãŒãéä¿¡ããããã®ãã¥ãŒ
2.ãã«ã¯-ãã¥ãŒã¹ã¬ã¿ãŒãéä¿¡ããããã®ãã¥ãŒ
3.ã»ããª-æ®ãã®ã¿ã¹ã¯ã®é çªã
ãããã£ãŠãsupervisor.dã§ã¯ãæ§æã¯æ¬¡ã®åœ¢åŒãåããŸãã
[program:celery] command=<PROJECT_ROOT>/venv/bin/celery worker -A conf.celery -l INFO --concurrency=4 --pidfile=/var/run/celery/celery.pid -Q celery ... [program:bulk] command=<PROJECT_ROOT>/venv/bin/celery worker -A conf.celery -l INFO --concurrency=4 --pidfile=/var/run/celery/bulk.pid -Q bulk ... [program:transactional] command=<PROJECT_ROOT>/venv/bin/celery worker -A conf.celery -l INFO --concurrency=4 --pidfile=/var/run/celery/transactional.pid -Q transactional ...
ããã§ã¯å€ãã®èšå®ãªãã·ã§ã³ãçç¥ãããŠããããšãç解ãããŸãã èŠããã«ãåã¿ãŒã³ã§ã¹ãŒããŒãã€ã¶ãŒã§ããã»ã¹ãå®è¡ããå¿ èŠããããšããããšã§ãã
ç¹å®ã®ãã¥ãŒã«ã¿ã¹ã¯ãèšå®ããæ¹æ³ã¯æ¬¡ã®ãšããã§ãã
send_message.apply_async(kwargs={'mailer': self, 'message': message}, queue='transactional')
ãã®çµæãè€æ°ã®ã¿ã¹ã¯ãåæã«åŠçã§ãããŠãŒã¶ãŒã¯æºè¶³ããŠããããã«èŠããŸãããµãŒãã¹ã¯ååã«é«éã«åäœããuwsgiã¯ãŒã«ãŒã¯ç¡æã§ãã
ã¿ã¹ã¯ã¹ããŒã¿ã¹ã®è¿œè·¡
ã¿ã¹ã¯ã®ã¹ããŒã¿ã¹ã®è¡šç€ºã¯éåžžã«ç°¡åã§ãã
celery -A conf.celery inspect scheduled celery -A conf.celery inspect active celery -A conf.celery inspect reserved
ãŸãããã䟿å©ãªç£èŠã®ããã«ã Flowerãœãªã¥ãŒã·ã§ã³ã䜿çšã§ããŸãã
ç§ã¯èªåèªèº«ãã»ããªã®è¶ å°é家ãšã¯èããŠããŸãããèªè ãããã䜿ã£ãçµéšãå ±æããŠããããå¬ããã§ãã
倧ããªããŒãã«ã®éæ£èŠå-é·æãšçæ
é ããæ©ãããããŒã¿ãèç©ããŸãã ãããŠã圌ãã¯äœãšãããŠåããªããã°ãªããŸããã
ç§ã®ãµãŒãã¹ã¯1ãæãããçŽ40äžä»¶ã®ã¡ãŒã«ãåŠçããŸãïŒããã¯éåžžã«å°ããã§ãïŒãã90æ¥ä»¥äžåã®ã¡ãŒã«ã«é¢ããããŒã¿ãæ¶å»ããŠããŸããã远跡ããŒã¿ãå«ãããŒãã«ã«ã¯çŽ200äžä»¶ã®ã¬ã³ãŒããæ®ã£ãŠããŸãã
ãããã®ããŒã¿ã¯çµ±èšã«è¡šç€ºããå¿ èŠããããããããæäœããå¿ èŠããããã¹ãããŒãç¹å®ããããã«ããããåæããå¿ èŠããããŸãã
æåã¯ãSQLã®ãã¹ãŠã®æšæºã«åŸã£ãŠæ§ç¯ãããããŒãã«ãè¶ æ£èŠåãããŸããã ããããçŸå®ã«ã¯ãORM Djangoã¯ããŒãã«éã®ããªãè€éãªJOINã®ããã«ãããªããšããªãã®ãããžã§ã¯ãã®ç掻ãå°é£ã«ããããšããããããŸãã
ããã«ãç¹ã«åå¿è ããããããŒã®å Žåãã¯ãšãªã»ãããç¹°ãè¿ããšãã«ã¯ãšãªãããã€çºçãããã¯å¿ ãããæããã§ã¯ãããŸããã
ãã®çµæãããªãåçŽãªã¯ãšãªãèšç®ããã«ã¯ïŒ
SELECT d.date, SUM(CASE WHEN me.status = 'SENT' THEN 1 ELSE 0 END) AS sent_count, SUM(CASE WHEN me.status = 'OPENED' THEN 1 ELSE 0 END) AS opened_count, SUM(CASE WHEN me.status = 'REDIRECTED' THEN 1 ELSE 0 END) AS redirected_count, SUM(CASE WHEN me.status = 'HARDBOUNCED' THEN 1 ELSE 0 END) AS hardbounced_count, SUM(CASE WHEN me.status = 'SOFTBOUNCED' THEN 1 ELSE 0 END) AS softbounced_count, SUM(CASE WHEN me.status = 'UNSUBSCRIBED' THEN 1 ELSE 0 END) AS unsubscribed_count FROM ( SELECT TO_CHAR(date_trunc('day', ('2017-03-01'::DATE - offs)), 'YYYY-MM-DD') AS date FROM generate_series(0, 30, 1) AS offs ) d LEFT OUTER JOIN MESSAGE_MESSAGEEVENT me ON (d.date=to_char(date_trunc('day', me.date_created), 'YYYY-MM-DD') AND status IN ('SENT', 'OPENED', 'REDIRECTED', 'HARDBOUNCED', 'SOFTBOUNCED', 'UNSUBSCRIBED') AND id IN (...)) GROUP BY date ORDER BY date
çŽ100ç§ããããŸããã æãããæéã§ããã ããã¯ããã€ãã®çç±ã§èµ·ãããŸããã
1.æåã«ãããã絶察ã«è¡ããªãã§ãã ããïŒ AND id INïŒ...ïŒé åã«çŽ100,000åã®èŠçŽ ãããå Žå:)
2.第äºã«ã d.date = to.charïŒdate_truncïŒ 'day'ãme.date_createdïŒã 'YYYY-MM-DD'ïŒãããd.date = me.date_created :: dateãšæžãæ¹ãã¯ããã«è¯ãã§ãã
3.第äžã«ãè¡šã«ã¯ä»ã®ããã€ãã®ããŒã®å€éšããŒããããŸããã JOINã®åäœãéåžžã«é ãããã
äœã«æ¥ãã®
1.å€éšããŒïŒéšåçã«éæ£èŠåãããããŒãã«ïŒã«å ããŠãé¢é£ã¬ã³ãŒãã®æ瀺çãªå€ã®ãã£ãŒã«ããè¿œå ããŸããã
2.ãªã¯ãšã¹ããæžãçŽããŸããã
SELECT d.date, SUM(CASE WHEN me.status = 'SENT' THEN 1 ELSE 0 END) AS sent, SUM(CASE WHEN me.status = 'OPENED' THEN 1 ELSE 0 END) AS opened, SUM(CASE WHEN me.status = 'HARDBOUNCED' THEN 1 ELSE 0 END) AS hardbounced, SUM(CASE WHEN me.status = 'SOFTBOUNCED' THEN 1 ELSE 0 END) AS softbounced, SUM(CASE WHEN me.status = 'UNSUBSCRIBED' THEN 1 ELSE 0 END) AS unsubscribed, SUM(CASE WHEN me.status = 'REDIRECTED' THEN 1 ELSE 0 END) AS redirected FROM ( SELECT ('2017-03-01'::DATE - offs)::date AS date FROM generate_series(0, 30, 1) AS offs ) d LEFT OUTER JOIN MESSAGE_MESSAGEEVENT me ON ( d.date=me.date_created::date AND me.date_created >= '2017-02-01 00:00:00' AND me.date_created <= '2017-03-01 23:59:59' AND true = true AND me.project_id = ... ) GROUP BY date ORDER BY date;
ãã®çµæããã®èŠæ±ã®åŠçæéã¯3ç§ã«ççž®ãããŸããããããã¯ãã§ã«ããªãèããããŸãã 説æããïŒ
GroupAggregate (cost=61552.47..82232.11 rows=200 width=11) (actual time=2445.092..2986.524 rows=31 loops=1) -> Merge Left Join (cost=61552.47..73614.51 rows=491920 width=11) (actual time=2445.048..2857.069 rows=69314 loops=1) Merge Cond: ((('2017-03-01'::date - offs.offs)) = ((me.date_created)::date)) -> Sort (cost=62.33..64.83 rows=1000 width=4) (actual time=0.127..0.174 rows=31 loops=1) Sort Key: (('2017-03-01'::date - offs.offs)) Sort Method: quicksort Memory: 26kB -> Function Scan on generate_series offs (cost=0.00..12.50 rows=1000 width=4) (actual time=0.034..0.069 rows=31 loops=1) -> Materialize (cost=61490.14..62719.94 rows=98384 width=15) (actual time=2444.913..2695.888 rows=69312 loops=1) -> Sort (cost=61490.14..61736.10 rows=98384 width=15) (actual time=2444.908..2539.290 rows=69312 loops=1) Sort Key: ((me.date_created)::date) Sort Method: external merge Disk: 2152kB -> Bitmap Heap Scan on message_messageevent me (cost=11442.78..51647.59 rows=98384 width=15) (actual time=1922.446..2253.982 rows=69312 loops=1) Recheck Cond: (project_id = ...) Filter: ((date_created >= '2017-02-01 00:00:00+01'::timestamp with time zone) AND (date_created <= '2017-03-01 23:59:59+01'::timestamp with time zone)) -> Bitmap Index Scan on message_messageevent_b098ad43 (cost=0.00..11418.19 rows=236446 width=0) (actual time=1870.742..1870.742 rows=284445 loops=1) Index Cond: (project_id = ...) Total runtime: 2988.107 ms
ãã®ãœãªã¥ãŒã·ã§ã³ã®æ¬ ç¹ã¯äœã§ããïŒ
ãŸããããŒãã«ã¯ããå€ãã®ãã£ã¹ã¯å®¹éãå æããŸãã 第äºã«ãéæ£èŠåãããããŒãã«ã®æååå€ãé¢é£ããå¿ èŠããããŸãã ã€ãŸã 芪ããŒãã«ã®æååèå¥åãå€æŽããå Žåã¯ãåã®éæ£èŠåããŒãã«ã§ãæååèå¥åãå€æŽããå¿ èŠããããŸãã 幞ããªããšã«ãDjangoã®ç§»è¡ã«ããããããéåžžã«ç°¡åã«å®è£ ã§ããŸãã
ã¢ããªã·ãã¯ãããžã§ã¯ãæ©èœã®ãã€ã¯ããµãŒãã¹ãžã®ç§»è¡
å€ããå°ãªãã倧èŠæš¡ãªãããžã§ã¯ããæé·ãããããšã®åé¡ã®1ã€ã¯ãç¶æããããšããŸããŸãé£ãããªãããšã§ãã ã³ãŒãããŒã¹ã¯æé·ãããã¹ãã®æ°ã¯å¢å ããŠããŸãã ããã«ããããžã§ã¯ãã®äžéšã§ãã¹ããããšãä»ã®äººããã®ããã«èŠããå¯èœæ§ããããŸãã ããšãã°ãã©ã³ãã ãªSyntaxErrorã¯ãããžã§ã¯ãå šäœãå§åããŸãã
ãŸããæ¢ã«èª¬æãã倧éã®ã¯ãšãªã«é¢é£ããããã©ãŒãã³ã¹ã®åé¡ã¯ããŸããŸãéèŠã«ãªã£ãŠããŸãã
解決çã¯ãæ©èœã®äžéšãåå¥ã®ãã€ã¯ããµãŒãã¹ã«åé¢ããããšã§ãã
1.ãã®æ©èœã«é¢é£ããã³ãŒãããŒã¹ãšãã¹ããå¥ã®ãããžã§ã¯ãã«å ¥ããŸãã
2.åå¥ã®uwsgiã€ã³ã¹ã¿ã³ã¹ãšãnginxã®åå¥ã®ã¢ããã¹ããªãŒã ãŸãã¯ãµãŒããŒãäœæããŸãã
3. RabbitMQãHTTP APIãªã©ã®ãããŒã«ãŒã䜿çšããŠã¡ã€ã³ãããžã§ã¯ãã«æ¥ç¶ããŸãã
...
4.å©çïŒ
ãã€ã¯ããµãŒãã¹ã¢ãŒããã¯ãã£ã®é·æïŒ
1.ããããã®æ©èœéšåã®ãšã©ãŒã«ãããããžã§ã¯ãã倱æããå±éºæ§ã¯ãªããªããŸããã
2.ä»ã®ãããžã§ã¯ãã§åå©çšã§ããŸãã ç¹ã«ãããããçš®é¡ã®ççž®ãªã³ã¯ãç»åå§çž®æ©ãªã©ã®ã¢ããªã±ãŒã·ã§ã³ãã€ã¯ããµãŒãã¹ã«é¢é£ããŠããŸãã
3.ãµãŒãã¹ã®äŸåé¢ä¿ãäºãã«ç¬ç«ããŠæŽæ°ã§ããŸãã ããšãã°ãã¡ã€ã³ãããžã§ã¯ãã§Djangoã®ã¬ã¬ã·ãŒããŒãžã§ã³ã䜿çšããå ŽåãéèŠãªã³ãŒãããŒã¹ãé©åãããå¿ èŠãªãããã€ã¯ããµãŒãã¹ã§æ°ããããŒãžã§ã³ã䜿çšã§ããŸãã
ç§ã®å Žåããããžã§ã¯ãã管çããããã«ç°¡åãªç®¡çããã«ãå®è£ ããå¿ èŠããããŸããã ãŠãŒã¶ãŒã«ãããµãŒãã¹ã®äœ¿çšã«é¢ããããªãéãã¬ããŒããå«ãŸããŠãããããåå¥ã«åäœããå¿ èŠããããã¡ã€ã³ãããžã§ã¯ãã®uwsgiã¯ãŒã«ãŒãæ»æããããããŸããã§ããã
ç§ã䜿çšããæè¡ïŒ
1.ã¡ã€ã³ãããžã§ã¯ãã®äžéšãšããŠHTTP APIãæ§ç¯ããããã®Django Rest Framework ïŒä»¥é-DRFïŒã
2.管çã·ã¹ãã ã®ã¯ã©ã€ã¢ã³ã-ReactJSãNodeJSãBabelãES6ãããã³ããã³ããšã³ãã®äžçããç§ã«ã¯ç解ã§ããªãèšèãæžãããã«ã
ãã«ãAPI
DRFã䜿çšãããšãæ¢åã®ãããžã§ã¯ãã§äŸ¿å©ãªREST APIãè¿ éã«æ§ç¯ã§ããŸãã ç§ã®å Žåã次ã®èŠä»¶ãæºããã€ã³ã¿ãŒãã§ãŒã¹ãäœæããå¿ èŠããããŸããã
1. APIãã³ãã«ãžã®ã¢ã¯ã»ã¹ã¯å®å šã§ãªããã°ãªããŸããã 管çè æš©éãæã€ç¹å®ã®ãŠãŒã¶ãŒã®ã¿ã管çã·ã¹ãã ã«ã¢ã¯ã»ã¹ã§ããŸãã
2.ã€ã³ã¿ãŒãã§ã€ã¹ã¯ãã¯ã©ã€ã¢ã³ãSPAã¢ããªã±ãŒã·ã§ã³ã«äŸ¿å©ãªå¯Ÿè©±æ¹æ³ãæäŸããå¿ èŠããããŸãã
3.ããã«äœ¿çšã§ããããã«ããã³ã¯ãªããžã§ã¯ãã®ãªã¹ããç¹å®ã®ãªããžã§ã¯ãã«é¢ããæ å ±ã®ååŸããªããžã§ã¯ãã®åé€ãªã©ã«æ©èœããå¿ èŠããããŸãã äžè¬ã«ãAPIã¯ã¯ã©ã€ã¢ã³ãåŽã§éåžžã®CRUDã€ã³ã¿ãŒãã§ã€ã¹ãæ§ç¯ããã®ã«ååãªã¯ãã§ãã
ãã°ã€ã³
ã¯ã©ã€ã¢ã³ãããã®èŠæ±ãæ¿èªããããã«ãDRFã®æ¢æã®ãœãªã¥ãŒã·ã§ã³-ããŒã¯ã³èªèšŒã䜿çšããŸããã
ãããæå¹ã«ããããã«å¿ èŠãªãã¹ãŠïŒ
1.ãrest_framework.authtokenããINSTALLED_APPSã«è¿œå ããŸãã
2.移è¡ãå®è¡ããŸãã
3.管çã·ã¹ãã ãžã®ã¢ã¯ã»ã¹æš©ãå¿ èŠãªãŠãŒã¶ãŒã®ããŒã¯ã³ãäœæããŸãã
次ã«ãå¿ èŠãªãã¥ãŒã»ããã«ããã¯ã¹ããå ±éã®ããã¯ã¹ã€ã³ãäœæããŸãã
# permissions.py from rest_framework.permissions import IsAdminUser class IsProjectAdminUser(IsAdminUser): def has_permission(self, request, view): return request.user.is_authenticated and request.user.is_admin
# views.py from rest_framework.authentication import TokenAuthentication from app.contrib.api.permissions import IsProjectAdminUser class AdminAuthMixin(object): authentication_classes = [TokenAuthentication] permission_classes = [IsProjectAdminUser]
ããã«ããã®ããã¯ã¹ã€ã³ã䜿çšããŠãæ¿èªãViewSetã«æ¥ç¶ããã ãã§ãã
... from app.contrib.api.views import AdminAuthMixin, MultiSerializerViewSet ... class CampaignsViewSet(AdminAuthMixin, MultiSerializerViewSet): queryset = Campaign.objects.filter(date_archived__isnull=True) filter_class = CampaignsFilter serializers = { 'list': CampaignsListSerializer, 'retrieve': CampaignDetailSerializer, }
顧客ã€ã³ã¿ã©ã¯ã·ã§ã³
axiosã©ã€ãã©ãªã¯ãã¯ã©ã€ã¢ã³ãåŽã§APIãšããåãããã®ã«æé©ã§ããã
APIãªã¯ãšã¹ãã®äŸïŒ
axios.get('http://example.com/api/entrypoint/', {params: this.state.query_params}) .then(response => { const items = response.data.results.map(obj => obj); this.setState({ is_loading: false, items: items, count: response.data.count }); });
Reactã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ããŒãã³ãã®ã³ãŒãå šäœãæäŸããŸããããªããªãã ããã¯ããªãæšæºã§ãã
PS
以äžã®èšäºã§ã¯ãlogstashãkibanaãelasticsearchã«åºã¥ãã倧èŠæš¡ãããžã§ã¯ãã§ãèŠåŽããã«ãã°ãæŽçããæ¹æ³ãããã³HelpScoutããã³GitBookãœãªã¥ãŒã·ã§ã³ã«åºã¥ãã¯ã©ã€ã¢ã³ãã®ããã¥ã¡ã³ããšãµããŒãã«è§Šããæ¹æ³ã«ã€ããŠèª¬æããŸãã
ãæž èŽããããšãããããŸããïŒ