
仿¥ã§ã¯ã
RESTful APIãå®å
šã«ååšããããã§ãã æ¯æãããäºçŽããŒãã«ãç°¡åãªéç¥ããä»®æ³ãã·ã³ã®å±éãŸã§-ã»ãŒãã¹ãŠãç°¡åãªHTTPã€ã³ã¿ã©ã¯ã·ã§ã³ãéããŠå©çšã§ããŸãã
ç¬èªã®ãµãŒãã¹ãéçºããŠããå Žåãå€ãã®å Žåãè€æ°ã®ãã©ãããã©ãŒã ã§åæã«åäœããããã«ãããããšããããŸãã OODïŒãªããžã§ã¯ãæåèšèšïŒã®æããã®ååã«ãããã³ãŒãã®åŸ©å
åãé«ãŸããæ¡åŒµæ§ãç°¡çŽ åãããŸãã
ãã®èšäºã§ã¯ãSOLIDïŒããã¯é åèªã§ãïŒãšåŒã°ããç¹å®ã®èšèšã¢ãããŒããæ€èšããŸãã
Slackçµ±åã䜿çšããŠãµãŒãã¹ãäœæããéã«å®éã«äœ¿çšãã
Twilioã§äœ¿çšããããã«æ¡åŒµããŸãã
ãã®ãµãŒãã¹ã¯ãããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã«ãŒããã©ã³ãã ã«éä¿¡ããŸãã ä»ããåäœã確èªãããå Žåã¯ã
ããžãã¯ãšããèšèã1-929-236-9306ã«éä¿¡ããŸãïŒç±³åœãšã«ããã®ã¿-MMSçµç±ã§ç»åãåä¿¡ããããããªãã¬ãŒã¿ãŒã®æéãé©çšãããå ŽåããããŸãïŒã
ãã¡ããã¯ãªãã¯ããŠãSlackçµç¹ã«åå ããããšãã§ã
ãŸã ã ãã°ã€ã³åŸã
/ magicãšå
¥åããŸãã
SOLID for Magic
SOLIDãåããŠäœ¿çšããå Žåãããã¯
ããã»ããŒãã£ã³ãããããæ®åããããªããžã§ã¯ãæåèšèšïŒOODïŒã®ååã®ã»ããã§ãã SOLIDã¯ä»¥äžã®é åèªã§ãã
- S-SRP-åäžè²¬ä»»ã®åå
- O-OCP-ãªãŒãã³ã¯ããŒãºãããªã³ã·ãã«
- L-LSP-ããŒãã©ãªã¹ã³ã代æ¿åç
- I-ISP-ã€ã³ã¿ãŒãã§ãŒã¹åé¢ã®åå
- D-DIP-äŸåæ§å転ã®åç
ãã®äžé£ã®ååã«åŸããšãã³ãŒãã®ãã©ãŒã«ããã¬ã©ã³ã¹ãåäžããæ¡åŒµæ§ãç°¡çŽ åãããŸãã èšäºã®åŸåã§ããããã®åååã«ã€ããŠè©³ãã説æããŸãã
ããŸããŸãªèšèªã®å€ãã®è¯ãäŸããããŸãã æåãª
Shape
ã
Circle
ã
Rectangle
ã
Area
äŸãç¹°ãè¿ã代ããã«ãå®äžçã®å®å
šã«æ©èœããã¢ããªã±ãŒã·ã§ã³ã§SOLIDã®å©ç¹ã瀺ããããšæããŸãã
ç§ã¯æè¿
Slack APIã§ãã¬ã€ããŸããã ã¹ã©ãã·ã¥ã䜿çšããŠç¬èªã®ããŒã ãäœæããã®ã¯éåžžã«ç°¡åã§ãã ç§ã¯
ããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã®å€§ãã¡ã³ã§ãããã®ã§ãããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã®ã©ã³ãã ã«ãŒãã®ç»åãçæããSlackã¹ã©ãã·ã¥ã³ãã³ããäœæãããšããã¢ã€ãã¢ãæãã€ããŸããã
ç§ã¯ããã«
Spring Bootã§èšç»ãå®è¡ããŸããã åŸã§èŠãããã«ãSpring Bootã¯ããã«äœ¿çšã§ããããã€ãã®åºãååã«åŸããŸãã
Twilioã«ã¯ãåªããé³å£°ããã³ããã¹ãã¡ãã»ãŒãžã³ã°APIããããŸãã Slackã®äŸãåãäžããŠTwilioãšçµ±åããããšãã©ãã»ã©ç°¡åããèŠãã®ã¯é¢çœããšæããŸããã ã¢ã€ãã¢ã¯ãããŒã ãšããã¹ãã¡ãã»ãŒãžãæ¢ç¥ã®é»è©±çªå·ã«éä¿¡ããããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã®ã©ã³ãã ãªç»åãååŸããããšã§ãã
以äžã¯ããã®ããã°ã©ãã³ã°æŒç¿äžã®å®éã®åäœäžã®SOLIDååïŒæ
éïŒã®å
èš³ã§ãã
ãã¹ãŠã®ã³ãŒãã¯
ããã«ãããŸã ã å¿
èŠã«å¿ããŠããã®ã³ãŒãããèªèº«ã®SlackãTwilioã¢ã«ãŠã³ãã«é©çšããæ¹æ³ã«ã€ããŠã¯åŸã»ã©èª¬æããŸãã
æåã®å®è¡ïŒSlackã䜿çšããããžãã¯
Spring Bootã䜿çšããŠMagicã¢ããªã±ãŒã·ã§ã³ãäœæãããšããäºå®ã ãã§ãç¹å¥ãªåŽåããããããšãªãã5ã€ã®SOLIDååã®ãã¡2ã€ãå³åº§ã«æäŸãããŸãã ãã ããæ£ããã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ã«ã€ããŠã¯åŒãç¶ã責任ããããŸãã
ã³ãŒãã®äœæããã»ã¹ã§ããŸããŸãªååãåŠç¿ãããããGitHubãããžã§ã¯ãã§å¯Ÿå¿ããã¿ã°ã確èªããããšã§ããã€ã§ãã³ãŒããµã³ãã«ãèŠãããšãã§ããŸãïŒããªãªãŒã¹ãã»ã¯ã·ã§ã³ã§ç¢ºèªã§ããŸãïŒã ãã®ç« ã®å®å
šãªã³ãŒãã¯ã
slack-first-pass
ã¿ã°ã§è¡šç€ºãããŸãã
SlackController
ã³ãŒããèŠãŠã¿ãŸãããïŒãã¹ãŠã®JavaãœãŒã¹ã¯ããã«ãããŸãïŒmagic-app / src / main / java / com / afitnerd / magicïŒãããã¯SOLIDã®
D
ãš
I
ååã®äŸã§ãã
@RestController @RequestMapping("/api/v1") public class SlackController { @Autowired MagicCardService magicCardService; @Autowired SlackResponseService slackResponseService; @RequestMapping( value = "/slack", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE ) public @ResponseBody Map<String, Object> slack(@RequestBody SlackSlashCommand slackSlashCommand) throws IOException { return slackResponseService.getInChannelResponseWithImage(magicCardService.getRandomMagicCardImage()); } }
DIPïŒäŸåé¢ä¿ã®é転ã®åå
DIPã®åçã¯æ¬¡ã®ãšããã§ãã
A.äžäœã¬ãã«ã®ã¢ãžã¥ãŒã«ã¯ãäžäœã¬ãã«ã®ã¢ãžã¥ãŒã«ã«äŸåããªãããã«ããŠãã ããã ã©ã¡ãã®ã¿ã€ãã®ã¢ãžã¥ãŒã«ãæœè±¡åã«äŸåããå¿
èŠããããŸãã
B.æœè±¡åã¯è©³çްã«äŸåããã¹ãã§ã¯ãããŸããã è©³çŽ°ã¯æœè±¡åã«äŸåããå¿
èŠããããŸãã
JavaãšSpring Bootã䜿çšãããšããã®ååãéåžžã«ç°¡åã«å®è£
ã§ããŸãã
SlackController
*å®è£
*
MagicCardService
ãµãŒãã¹ã ããã¯ãJavaã€ã³ã¿ãŒãã§ãŒã¹ã§ããããã*æœè±¡å*ã§ãã ããã¯ã€ã³ã¿ãŒãã§ãŒã¹ã§ããããã詳现ã¯ãããŸããã
MagicCardService
ã®å®è£
ã¯
MagicCardService
ãŸããã åŸã»ã©ãã¢ããªã±ãŒã·ã§ã³ãã¢ãžã¥ãŒã«ã«åå²ããŠãã€ã³ã¿ãŒãã§ã€ã¹ãšãã®å®è£
ããã®ããã«åé¢ããæ¹æ³ã確èªããŸãã Spring Bootã§äŸåé¢ä¿ãå®è£
ããä»ã®çŸä»£çãªæ¹æ³ãèŠãŠãããŸãã
ISPïŒã€ã³ã¿ãŒãã§ãŒã¹åé¢ã®åç
ISPã®ååã¯æ¬¡ã®ããã«è¿°ã¹ãŠããŸãã
å€ãã®åå¥ã®ã¯ã©ã€ã¢ã³ãã€ã³ã¿ãŒãã§ã€ã¹ã¯ã1ã€ã®ãŠãããŒãµã«ã€ã³ã¿ãŒãã§ã€ã¹ãããåªããŠããŸãã
SlackController
MagicCardService
ãš
SlackResponseService
2ã€ã®åå¥ã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ã
SlackResponseService
ã ãããã®1ã€ã¯ãããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã®ãµã€ããšå¯Ÿè©±ããŸãã å¥ã®äººã¯Slackãšå¯Ÿè©±ããŸãã ããã2ã€ã®åå¥ã®æ©èœãå®è¡ããåäžã®ã€ã³ã¿ãŒãã§ã€ã¹ãäœæãããšãISPã®ååã«éåããŸãã
次ïŒTwilioã§ã®ãããžãã¯ã
ãã®ç« ã®ã³ãŒãã远跡ããã«ã¯ã
twilio-breaks-srp
ã¿ã°ãåç
§ããŠãã ããã
TwilioControllerã³ãŒããèŠãŠãã ããïŒ
@RestController @RequestMapping("/api/v1") public class TwilioController { private MagicCardService magicCardService; static final String MAGIC_COMMAND = "magic"; static final String MAGIC_PROXY_PATH = "/magic_proxy"; ObjectMapper mapper = new ObjectMapper(); private static final Logger log = LoggerFactory.getLogger(TwilioController.class); public TwilioController(MagicCardService magicCardService) { this.magicCardService = magicCardService; } @RequestMapping(value = "/twilio", method = RequestMethod.POST, headers = "Accept=application/xml", produces=MediaType.APPLICATION_XML_VALUE) public TwilioResponse twilio(@ModelAttribute TwilioRequest command, HttpServletRequest req) throws IOException { log.debug(mapper.writeValueAsString(command)); TwilioResponse response = new TwilioResponse(); String body = (command.getBody() != null) ? command.getBody().trim().toLowerCase() : ""; if (!MAGIC_COMMAND.equals(body)) { response .getMessage() .setBody("Send\n\n" + MAGIC_COMMAND + "\n\nto get a random Magic the Gathering card sent to you."); return response; } StringBuffer requestUrl = req.getRequestURL(); String imageProxyUrl = requestUrl.substring(0, requestUrl.lastIndexOf("/")) + MAGIC_PROXY_PATH + "/" + magicCardService.getRandomMagicCardImageId(); response.getMessage().setMedia(imageProxyUrl); return response; } @RequestMapping(value = MAGIC_PROXY_PATH + "/{card_id}", produces = MediaType.IMAGE_JPEG_VALUE) public byte[] magicProxy(@PathVariable("card_id") String cardId) throws IOException { return magicCardService.getRandomMagicCardBytes(cardId); } }
åã«è¿°ã¹ãããã«ãäžæ¯ã®å®è£
ã«ã¯ããçŸä»£çãªã¢ãããŒããé©çšããŸãïŒãã¹ããã©ã¯ãã£ã¹ïŒã ã芧ã®ãšãããSpring Boot Constructor Injectionã§ãããè¡ããŸããã ããã¯ãSpring Bootã®ææ°ããŒãžã§ã³ã§ã¯ãäŸåé¢ä¿ã®æ³šå
¥ã次ã®ããã«è¡ãããããšã瀺ãè¯ãæ¹æ³ã§ãã
1.ã¯ã©ã¹ã«1ã€ä»¥äžã®é衚瀺ãã£ãŒã«ããèšå®ããŸããæ¬¡ã«äŸã瀺ããŸãã
private MagicCardService magicCardService;
2.ã»ããã®é衚瀺ãã£ãŒã«ãã®ã³ã³ã¹ãã©ã¯ã¿ãŒãå®çŸ©ããŸãã
public TwilioController(MagicCardService magicCardService) { this.magicCardService = magicCardService; }
Spring Bootã¯ãå®è¡æã«ãªããžã§ã¯ãã®å®è£
ãèªåçã«åŠçããŸãã å©ç¹ã¯ãããã§ãã³ã³ã¹ãã©ã¯ã¿ãŒå
ã®åã蟌ã¿ãªããžã§ã¯ãã§ãšã©ãŒãã§ãã¯ãšæ€èšŒãå®è¡ã§ããããšã§ãã
ã³ã³ãããŒã©ãŒã«ã¯ã
/twilio
ãš
/magic_proxy/{card_id}
2ã€ã®éšåãå«ãŸããŠããŸãã magic_proxyãã¹ã«ã¯å°ã説æãå¿
èŠãªã®ã§ããŸãSRPã®ååã«éåããããšã«ã€ããŠèª¬æããåã«èª¬æããŸãã
TwiMLããæ¥œãã¿ãã ãã
TwiMLã¯TwilioããŒã¯ã¢ããèšèªã§ãã TwiMLã¯Twilioã®æç€ºã§ãããããããã¯ãã¹ãŠã®Twilioã®åçã®åºç€ã§ãã ãŸããXMLã§ãã éåžžãããã¯åé¡ã§ã¯ãããŸããã ãã ããMagic the Gatheringãµã€ãããè¿ãããURLã¯ãTwiMLããã¥ã¡ã³ãã«å«ããã«ã¯åé¡ããããŸãã
Magic the Gatheringãããç»åãæœåºãããURLã¯æ¬¡ã®ããã«ãªããŸãã
http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=144276&type=card
URLã®ã¢ã³ããµã³ãïŒïŒïŒã«æ³šæããŠãã ããã ã¢ã³ããµã³ããXMLããã¥ã¡ã³ãã«åã蟌ãã«ã¯ã2ã€ã®æå¹ãªæ¹æ³ãããããŸããã
1.ãšã¹ã±ãŒãæå
<Response> <Message> <Body/> <Media>http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=144276&âamp;type=card</Media> </Message> </Response>
ããã§ã¯ãã¢ã³ããµã³ãã®ä»£ããã«ã
&âamp;
ã
2. CDATAãã©ã°ã¡ã³ãïŒæåããŒã¿ïŒ
<Response> <Message> <Body/> <Media> <![CDATA[http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=144276&type=card]]> </Media> </Message> </Response>
ãããã®ãªãã·ã§ã³ã¯ãããããSpring Bootã«çµã¿èŸŒãŸããJackson JSONããã»ããµãŒã®
Jackson Dataformat XMLæ¡åŒµæ©èœã䜿çšããŠãJavaã§ç°¡åã«å®è£
ã§ããŸãã
åé¡ã¯ãWizards of the Coast Webãµã€ãïŒããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã¡ã³ãããŒïŒããç»åãåä¿¡ãããšãã«æåã®ãªãã·ã§ã³ããšã©ãŒã«ãªããTwilioã§ã¯2çªç®ã®ãªãã·ã§ã³ããµããŒããããªãããšã§ãïŒã¡ãã£ãšTwilioïŒTwiMLã§CDATAãµããŒããå®è£
ããŸããïŒïŒ
ãªã¯ãšã¹ãã®ãããã·ã䜿çšããŠãã®å¶éãåé¿ããŸããã ãã®å ŽåãTwiMLã³ãŒããçæãããŸãïŒ
<Response> <Message> <Body/> <Media> http://<my magic host>/api/v1/magic_proxy/144276 </Media> </Message> </Response>
ãã®ãããªã³ãŒããåãåããšãTwilioã¯ãšã³ããã€ã³ã
/magic_proxy
åç
§ãããã§ã«èå°è£ã§ãããã·ãMagic the Gathering Webãµã€ãããç»åãåä¿¡ããŠââçºè¡ããŸãã
çŸåšãSOLIDã®åçã®ç ç©¶ãç¶ããŠããŸãã
SRPïŒåç¬ã®è²¬ä»»åå
SRPã®ååã¯æ¬¡ã®ãšããã§ãã
ã¯ã©ã¹ã«ã¯1ã€ã®é¢æ°ã®ã¿ãå¿
èŠã§ãã
äžèšã®ã³ã³ãããŒã©ãŒã¯ãã®ãŸãŸåäœããŸãããTwiMLå¿çãšç»åã®ãããã·ã®äž¡æ¹ãè¿ããããSRPã«éåããŸãã
ãã®äŸã§ã¯ããã¯å€§ããªåé¡ã§ã¯ãããŸããããç¶æ³ãããã«æã«è² ããªããªãããšã¯å®¹æã«æ³åã§ããŸãã
twilio-fixes-srp
ã¿ã°ã«åŸããšã
twilio-fixes-srp
ãšåŒã°ããæ°ããã³ã³ãããŒã©ãŒã衚瀺ãã
MagicCardProxyController
ã
@RestController @RequestMapping("/api/v1") public class MagicCardProxyController { private MagicCardService magicCardService; public MagicCardProxyController(MagicCardService magicCardService) { this.magicCardService = magicCardService; } @RequestMapping(value = MAGIC_PROXY_PATH + "/{card_id}", produces = MediaType.IMAGE_JPEG_VALUE) public byte[] magicProxy(@PathVariable("card_id") String cardId) throws IOException { return magicCardService.getRandomMagicCardBytes(cardId); } }
ãã®å¯äžã®ã¿ã¹ã¯ã¯ãMagic the Gathering Webãµã€ããããããã·ã§åä¿¡ããç»åã®ãã€ããè¿ãããšã§ãã
çŸåšãå¯äžã®
TwilioController
颿°ã¯TwiMLã³ãŒããçºè¡ããããšã§ãã
DIPå®è£
çšã®ã¢ãžã¥ãŒã«
Mavenã䜿çšãããšããããžã§ã¯ããã¢ãžã¥ãŒã«ã«ç°¡åã«åå²ã§ããŸãã ã¹ã³ãŒãã¯ç°ãªã£ãŠããŠãããŸããŸããããã³ã³ãã€ã«ïŒããã©ã«ãïŒãå®è¡ããã¹ããšããåãã¹ã³ãŒãããããŸãã
ã¢ãžã¥ãŒã«ããã®ãšãªã¢ã§ã¢ã¯ãã£ãã«ãªããšããšãªã¢ãå¶åŸ¡ãååŸããŸãã
runtime
ã¹ã³ãŒãã¯ãç¹å®ã®ã¢ãžã¥ãŒã«ã®ã¯ã©ã¹ãã³ã³ãã€ã«æã«*å©çšã§ããªã*ããšã確èªããŸãã ãããã¯å®è¡æã«ã®ã¿å©çšå¯èœã§ãã ããã¯ãDIPååã®å®è£
ã«åœ¹ç«ã¡ãŸãã
äŸã§ç°¡åã«è¡šç€ºã§ããŸãã
modules-ftw
ã¿ã°ã®ã³ãŒãã確èªããŠãã ããã ãããžã§ã¯ãã®æ§æãæ ¹æ¬çã«å€æŽãããããšãããããŸãïŒIntelliJã§èŠãããããã«ïŒã

çŸåšã4ã€ã®ã¢ãžã¥ãŒã«ããããŸãã
magic-app
ã¢ãžã¥ãŒã«ãèŠããšã
pom.xml
ããä»ã®ã¢ãžã¥ãŒã«ã«ã©ã®ããã«äŸåããŠããããããããŸãã
<dependencies> ... <dependency> <groupId>com.afitnerd</groupId> <artifactId>magic-config</artifactId> </dependency> <dependency> <groupId>com.afitnerd</groupId> <artifactId>magic-api</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>com.afitnerd</groupId> <artifactId>magic-impl</artifactId> <scope>runtime</scope> </dependency> </dependencies>
magic-impl
ã¯
runtime
ã«ããã
magic-api
ã¯
compile
ããšã«æ³šæããŠãã ããã
TwilioController
èªåçã«ãã€ã³ãããŸãã
@RestController @RequestMapping(API_PATH) public class TwilioController { private TwilioResponseService twilioResponseService; ⊠}
å®è£
ãããã¯ã©ã¹ããã®æ¹æ³ã§èªåçã«ãã€ã³ãããããšãããšã©ããªããèŠãŠã¿ãŸãããã
@RestController @RequestMapping(API_PATH) public class TwilioController { private TwilioResponseServiceImpl twilioResponseService; ⊠}

IntelliJã¯TwilioResponseServiceImplã¯ã©ã¹ãèŠã€ããããšãã§ããŸãããããã¯ã
compile
ã¹ã³ãŒãå
ã«*ãªã*ããã§ãã
楜ãã¿ã®ããã«ã
pom.xml
ãã
runtime
è¡ãåé€ããŠã¿ãŠãã ãã
TwilioResponseServiceImpl
ã¯ã©ã¹ãèŠã€ããŠ
TwilioResponseServiceImpl
ããšã
TwilioResponseServiceImpl
ãŸãã
ãããŸã§èŠãŠããããã«ãmavenã¢ãžã¥ãŒã«ãšã¹ã³ãŒããçµã¿åãããããšã§ãDIPã®ååãå®è£
ã§ããŸãã
ãã£ããã·ã¥ã©ã€ã³ïŒã¹ã©ãã¯ãªãã¡ã¯ã¿ãªã³ã°
ãã®ã¢ããªã±ãŒã·ã§ã³ãåããŠäœæãããšããSOLIDã«ã€ããŠã¯èããŸããã§ããã Slackã¢ããªãããã¯ããŠãã¹ã©ãã·ã¥ã³ãã³ãã®æ©èœãæäœãããã£ãã ãã§ãã
æåã®ããŒãžã§ã³ã§ã¯ããã¹ãŠã®Slacké¢é£ã®ãµãŒãã¹ãšã³ã³ãããŒã©ãŒã¯
Map<String, Object>
è¿ããŸããã ããã¯ãSpring Bootã¢ããªã±ãŒã·ã§ã³ã«ãšã£ãŠè¯ãããªãã¯ã§ããå¿çã®æ§é ãè¡šãæ£åŒãªJavaã¢ãã«ãå¿é
ããããšãªããJSONå¿çãçºè¡ã§ããŸãã
ã¢ããªã±ãŒã·ã§ã³ã®éçºã«äŒŽããèªã¿åãå¯èœã§ä¿¡é Œæ§ã®é«ãã³ãŒãã®ããæ£åŒãªã¢ãã«ãäœæããããšããèŠæããããŸããã
slack-violates-lsp
ã®ãœãŒã¹ã³ãŒããåç
§ããŠãã ããã
magic-api
ã¢ãžã¥ãŒã«ã®
SlackResponse
ã¯ã©ã¹ãèŠãŠã¿ãŸãããã
public abstract class SlackResponse { private List<Attachment> attachments = new ArrayList<>(); @JsonInclude(JsonInclude.Include.NON_EMPTY) public List<Attachment> getAttachments() { return attachments; } @JsonInclude(JsonInclude.Include.NON_NULL) public abstract String getText(); @JsonProperty("response_type") public abstract String getResponseType(); ... }
ããã§ã
SlackResponse
ã¯ã©ã¹ã«ã¯
Attachments
é
åãããã¹ãæååãããã³
response_type
æååãããããšã
SlackResponse
ãŸãã
SlackResponse
ã¯å
abstract
宣èšãã
getText
ããã³
getResponseType
ã¡ãœããã®å®è£
颿°ã¯åã¯ã©ã¹ã«åé¡ãããŸãã
SlackInChannelImageResponse
åã¯ã©ã¹ã®1ã€ãèŠãŠã¿ãŸãããã
public class SlackInChannelImageResponse extends SlackResponse { public SlackInChannelImageResponse(String imageUrl) { getAttachments().add(new Attachment(imageUrl)); } @Override public String getText() { return null; } @Override public String getResponseType() { return "in_channel"; } }
getText()
ã¡ãœããã¯
null
è¿ã
null
ã ãã®åçã§ã¯ãåçã«ã¯*ç»åã®ã¿ãå«ãŸããŸãã ããã¹ãã¯ããšã©ãŒã¡ãã»ãŒãžã®å Žåã«ã®ã¿è¿ãããŸãã ãã*æããã«* LSPã®ã«ãããããŸãã
LSPïŒããŒãã©ãªã¹ã¯ä»£æ¿åç
LSPã®ååã¯æ¬¡ã®ãšããã§ãã
ããã°ã©ã å
ã®ãªããžã§ã¯ãã¯ãããã°ã©ã ã®ç²ŸåºŠã倿Žããã«ãµãã¿ã€ãã«çœ®ãæããããšãã§ããå¿
èŠããããŸãã
ç¶æ¿éå±€ãåŠçããŠããŠãåã¯ã©ã¹*ãåžžã«* nullãè¿ãå Žåãããã¯LSPååéåã®æç¢ºãªå
åã§ãã åã¯ã©ã¹ã«ã¯ãã®ã¡ãœããã¯å¿
èŠãããŸãããã芪ã¯ã©ã¹ã«èšè¿°ãããŠããã€ã³ã¿ãŒãã§ã€ã¹ã®ããã«å®è£
ããå¿
èŠãããããã§ãã
GitHubãããžã§ã¯ãã®
master
ãã©ã³ããã芧ãã ããã ããã§ã
SlackResponse
éå±€ã¯LSPã«äžèŽããããã«
SlackResponse
ããŸããã
public abstract class SlackResponse { @JsonProperty("response_type") public abstract String getResponseType(); }
ããã§ãå®è£
ããå¿
èŠããããã¹ãŠã®åã¯ã©ã¹ã«å
±éããå¯äžã®ãã®ã¯
getResponseType()
ã¡ãœããã§ãã
SlackInChannelImageResponse
ã¯ã©ã¹ã«ã¯ãæ£è§£ã«å¿
èŠãªãã¹ãŠã®åçãå«ãŸããŠããŸãã
public class SlackInChannelImageResponse extends SlackResponse { private List<Attachment> attachments = new ArrayList<>(); public SlackInChannelImageResponse(String imageUrl) { attachments.add(new Attachment(imageUrl)); } public List<Attachment> getAttachments() { return attachments; } @Override public String getResponseType() { return "in_channel"; } ⊠}
åã³
null
ãè¿ãå¿
èŠã¯ãããŸããã
ãã1ã€å°ããªæ¹åããããŸãã以åã
SlackResponse
ã¯ã©ã¹ã«ããã€ãã®JSONã¢ãããŒã·ã§ã³ããã
SlackResponse
ã
@JsonInclude(JsonInclude.Include.NON_EMPTY)
ãš
@JsonInclude(JsonInclude.Include.NON_NULL)
ã§ãã
å€ããŒãã®ç©ºã®æ·»ä»ãã¡ã€ã«é
åãŸãã¯ããã¹ããã£ãŒã«ããJSONã«å
¥ããªãããšãä¿èšŒããããã«å¿
èŠã§ããã ãããã¯åŒ·åãªæ³šéã§ããããã®ãããã¢ãã«ã®ãªããžã§ã¯ãã¯è匱ã«ãªããä»ã®éçºè
ã«ã¯äœãèµ·ãã£ãŠããã®ããã¯ã£ããããªãå ŽåããããŸãã
OCPïŒãªãŒãã³/ã¯ããŒãºãåå
SOLIDã§ã®æ
ã®æåŸã®ååã¯OCPã§ãã
OCPã®ååã¯æ¬¡ã®ãšããã§ãã
ãœãããŠã§ã¢ãšã³ãã£ãã£...ã¯æ¡åŒµã®ããã«éãããŠããå¿
èŠããããŸããã倿Žã®ããã«éããããŠããå¿
èŠããããŸãã
èãæ¹ã¯ãåç
§æ¡ä»¶ã倿Žãããšããã¯ã©ã¹ãæ¡åŒµããæ¢åã®ã¯ã©ã¹ã«ã³ãŒãã远å ããªãå Žåãã³ãŒãã¯æ°ããèŠä»¶ã«ãã广çã«å¯ŸåŠãããšããããšã§ãã ããã«ãããã³ãŒããå¿ã³å¯ãã®ãé²ããŸãã
äžèšã®äŸã§ã¯ã
SlackResponse
ã¯ã©ã¹ã倿Žãã远å ã®çç±ã¯ãããŸããã ä»ã®ã¿ã€ãã®Slackåçã¿ã€ãã®ãµããŒããã¢ããªã±ãŒã·ã§ã³ã«è¿œå ããå Žåããã®ç¹ç°æ§ããµãã¯ã©ã¹ã§ç°¡åã«èª¬æã§ããŸãã
ããã§ããSpring Bootã®åŒ·ãã¯æããã§ãã
magic-impl
SlackResponseServiceImpl
ã¯ã©ã¹ãèŠãŠãã ããã
@Service public class SlackResponseServiceImpl implements SlackResponseService { MagicCardService magicCardService; public SlackResponseServiceImpl(MagicCardService magicCardService) { this.magicCardService = magicCardService; } @Override public SlackResponse getInChannelResponseWithImage() throws IOException { return new SlackInChannelImageResponse(magicCardService.getRandomMagicCardImageUrl()); } @Override public SlackResponse getErrorResponse() { return new SlackErrorResponse(); } }
ã€ã³ã¿ãŒãã§ãŒã¹ã«ãããšã
getInChannelResponseWithImage
ããã³
getErrorResponse
ã¯
SlackResponse
ãªããžã§ã¯ããè¿ããŸãã
ãããã®ã¡ãœããå
ã§ãããŸããŸãª
SlackResponse
åãªããžã§ã¯ãã
SlackResponse
ãŸãã Spring BootãšJSONã«çµã¿èŸŒãŸããŠããjackson-mapperã¯ãç¹å®ã®ãªããžã§ã¯ãã«å¯ŸããŠæ£ããJSONãçæããã®ã«ååãªã»ã©ã¹ããŒãã§ãããããã¯å
éšçã«ç¹åŸŽä»ããããŠããŸãã
Slackã§çµç¹ã«çµ±åãæäŸããããTwilioã¢ã«ãŠã³ãã®ãµããŒãïŒãŸãã¯ãã®äž¡æ¹ïŒãå®è£
ããå Žåã¯ãå
ã«é²ãã§ãã ããïŒ ãã以å€ã®å Žåã¯ãèšäºã®æåŸã«ããå±¥æŽæžã«ã¢ã¯ã»ã¹ã§ããŸãã
ã¢ããªã±ãŒã·ã§ã³ã®å±é
ãã®ã¢ããªã±ãŒã·ã§ã³ãæå€§éã«äœ¿çšããå Žåã¯ãHerokuã«ã¢ããªã±ãŒã·ã§ã³ããããã€ããåŸãSlackãšTwilioãé©åã«æ§æããå¿
èŠããããŸãã
ãŸãã¯ãSlackãŸãã¯Twilioãã€ã³ã¹ããŒã«ã§ããŸãã ãããã®å Žåã§ããæåã«è¡ãå¿
èŠãããã®ã¯ãã¢ããªã±ãŒã·ã§ã³ãHerokuã«ãããã€ããããšã§ãã 幞ããªããšã«ãããã¯ç°¡åã§ãã
Herokuã®å±é
ã¢ããªã±ãŒã·ã§ã³ãHerokuã«ãããã€ããæãç°¡åãªæ¹æ³
ã¯ãGitHubãããžã§ã¯ãã®READMEã»ã¯ã·ã§ã³ã«ãã玫è²ã®ãã¬ã³ããªãŒãªãã¿ã³ãã¯ãªãã¯ããããšã§ãã
BASE_URL
ãš
SLACK_TOKENS
2ã€ã®è©³çްãæå®ããå¿
èŠããããŸãã
BASE_URL
ã¯ãHerokuã¢ããªã±ãŒã·ã§ã³ã®å®å
šãªãã¹ãšååã§ãã ããšãã°ã
https ïŒ
//random-magic-card.herokuapp.comã«ã¢ããªã±ãŒã·ã§ã³ãã€ã³ã¹ããŒã«ããŠã
ãŸã ã ã¢ããªã±ãŒã·ã§ã³åãéžæãããšãã¯ã
https://<app name>.herokuapp.com
ãšåã圢åŒã«åŸã
https://<app name>.herokuapp.com
ã
Herokuã«ã¯Slackããã®æ
å ±ãå¿
èŠã§ãããSlackã®çµ±åã«ã¯Herokuã«ã€ããŠã®æ
å ±ãå¿
èŠã§ãããããããã«ã¯é¶åµã«é¢ããåé¡ããããŸãã æåã¯ãããã©ã«ãå€ã
SLACK_TOKENS
ãã£ãŒã«ãã«æ®ããŠããããšãã§ããŸããåŸã§ãã®å€ãè¿ããçŸåšã®Slack APIããŒã¯ã³ã§æŽæ°ããŸãã
https://<app name>.herokuapp.com
ããŠãã€ã³ã¹ããŒã«ã確èªã§ã
https://<app name>.herokuapp.com
ã ãã©ãŠã¶ã«ããžãã¯ã¶ã®ã£ã¶ãªã³ã°ããããã©ã³ãã ã«è¡šç€ºãããŸãã ãšã©ãŒãçºçããå Žåã¯ãHerokuã¢ããªã±ãŒã·ã§ã³ã®Webã€ã³ã¿ãŒãã§ã€ã¹ã§ãšã©ãŒãã°ã確èªããŠãã ããã
åäœäžã®Webã€ã³ã¿ãŒãã§ã€ã¹ã®äŸã次ã«ç€ºããŸã ã
ã¹ã©ãã¯ã®ã»ããã¢ãã
https://api.slack.com/appsã«ç§»åãã[
Create New App
ã®
Create New App
]ãã¿ã³ãã¯ãªãã¯ããŠéå§ããŸãã

åå
App Name
ãå
¥åããã¢ããªã±ãŒã·ã§ã³ã远å ãã
Workspace
ãéžæããŸãã

次ã«ãå·ŠåŽã®ã¹ã©ãã·ã¥ã³ãã³ã
Slash Commands
ãªã³ã¯ãã¯ãªãã¯ããŸããæ°ããã³ãã³ããäœæããããã®ãã¿ã³ããããŸãæ°ããã³ãã³ãã®
Create New Command
ïŒ

ã³ãã³ãã®å€ïŒäŸïŒ
/magic
ïŒã
Request URL
ïŒäŸïŒ
https://<your app name>.herokuapp.com/api/v1/slack
ïŒããã³çã説æãå
¥åããŸãã æ¬¡ã«[
Save
]ãã¯ãªãã¯ã
Save
ã

Slackã®ã¹ã©ãã·ã¥ã³ãã³ããå®å
šã«æ§æãããŸããã

å·Šãã€ã³ã®
Install app to your workspace section
Basic Information
ã»ã¯ã·ã§ã³ã«ç§»åããç»é¢äžã®
Install app to your workspace section
ã«
Install app to your workspace section
ã®
Install app to your workspace section
ãå±éããŸãã [
Install app to Workspace
]ãã¿ã³ãã¯ãªãã¯ããŸãã

次ã«ãæ¿èªã®ããã®ãã¿ã³ïŒ

æ»ã£ã
Basic Information
ç»é¢ãŸã§ã¹ã¯ããŒã«ããæ€èšŒããŒã¯ã³ãèšé²ããŸãã

Heroku CLIãã€ã³ã¹ããŒã«ããå Žåãæ¬¡ã®ã³ãã³ãã§
SLACK_TOKENS
ããããã£
SLACK_TOKENS
æ£ããèšå®ã§ããŸãã
heroku config:set \ SLACK_TOKENS=<comma separated tokens> \
ãŸãã¯ã
Herokuããã·ã¥ããŒãã«ç§»åããã¢ããªã±ãŒã·ã§ã³ã«ç§»åããŠãèšå®ã®
SLACK_TOKENS
å€ã倿ŽããŸãã
ããã§ãçµç¹ã®Slackãã£ã³ãã«ã§ã¹ã©ãã·ã¥ã³ãã³ããæ©èœããã¯ãã§ãããã®ä»£ããã«ãããžãã¯ã¶ã®ã£ã¶ãªã³ã°ã«ãŒããåãåããŸãã

Twilioã®ã»ããã¢ãã
Twilioçµ±åãæ§æããã«
ã¯ãã³ã³ãœãŒã«ã®Twilioããã·ã¥ããŒãã«ç§»åããŸãã

çç¥èšå·ãã¯ãªãã¯ããŠã
Programmable SMS
ãéžæããŸãã

Messaging Services
éžæã
Messaging Services
ã

èµ€ããã©ã¹ã®ä»ãããã¿ã³ãã¯ãªãã¯ããŠãæ°ããã¡ãã»ãŒãžã³ã°ãµãŒãã¹ãäœæããŸãïŒãµãŒãã¹ããŸã ãªãå Žåã¯[æ°ããã¡ãã»ãŒãžã³ã°ãµãŒãã¹ã®äœæ]ãã¯ãªãã¯ããŸãïŒã

Friendly Name
å
¥åãã[
Use Case
]åã§[
Notifications, 2-Way
Friendly Name
ãéžæãã[
Create
]ãã¯ãªãã¯ã
Create
ã

Process Inbound Messages
ãã§ãã¯ããŒã¯ã確èªããHerokuã¢ããªã±ãŒã·ã§ã³ã®
Request URL
ã
Request URL
ããŸãïŒäŸ
https://<your app name>.herokuapp.com/api/v1/twilio
ïŒïŒ

[
Save
]ãã¿ã³ãã¯ãªãã¯ããŠã倿Žãä¿åããŸãã
å·Šã¡ãã¥ãŒã®[
Numbers
ã»ã¯ã·ã§ã³ã«ç§»åããã¡ãã»ãŒãžã³ã°ãµãŒãã¹ã«Twilioçªå·ã远å ãããŠããããšã確èªããŸãã

ããã§ã
magic
ãšããåèªãçªå·ã«ããã¹ãã¡ãã»ãŒãžãšããŠéä¿¡ããŠãTwilioãµãŒãã¹ããã¹ãã§ããŸãã

**泚ïŒ**倧æåãšå°æåãåããã
magic
ãšããåèªä»¥å€ãéä¿¡ãããšãäžèšã®ãšã©ãŒã¡ãã»ãŒãžã衚瀺ãããŸãã
ãœãªãããµããªãŒ
ããäžåºŠãSOLIDããŒãã«ãå
¬éããŸããä»åã¯ãåååã«äžèŽããGithubãããžã§ã¯ãã¿ã°ã䜿çšããŸãã
- S-SRP-å¯äžã®è²¬ä»»ã®ååã ã¿ã°ïŒ
twilio-fixes-srp
TwilioController
ã³ã³ãããŒã©ãŒã2ã€ã®éšåã«åå²ããŸããåã³ã³ãããŒã©ãŒã«ã¯1ã€ã®æ©èœãããããŸããã - O-OCP-ãªãŒãã³ãã¹/ã¯ããŒãºãã¹ã®ååã ã¿ã°ïŒ
master
SlackResponse
ã¯ã©ã¹SlackResponse
ã¯ã³ããŒã¹ã§ããã倿Žã§ããŸããã æ¢åã®ãµãŒãã¹ã®ã³ãŒãã倿Žããã«æ¡åŒµã§ããŸãã - L-LSP-ããŒãã©ãªã¹ã³ãã®çœ®æåçã ã¿ã°ïŒ
master
SlackResponse
åã¯ã©ã¹ã¯ã©ããnull
è¿ãããäžèŠãªã¯ã©ã¹ã泚éã¯å«ãŸããŸããã - I-ISP-ã€ã³ã¿ãŒãã§ãŒã¹ã®åé¢ã®åçã ã¿ã°ïŒ
slack-first-pass
master
SlackResponseService
ãšSlackResponseService
ã¯ç°ãªãæ©èœãå®è¡ãããããäºãã«åé¢ãããŠããŸãã - D-DIP-äŸåé¢ä¿ã®å転ã®åçã ã¿ã°ïŒ
slack-first-pass
master
äŸåãµãŒãã¹ã¯èªåçã«ã³ã³ãããŒã©ãŒã«ãã€ã³ããããŸãã ã³ã³ãããŒã©ãŒã®å®è£
ã¯ãäŸåæ§æ³šå
¥ã®ããã¹ããã©ã¯ãã£ã¹ãã§ãã
ãã®ã¢ããªã±ãŒã·ã§ã³ã®éçºã«ã¯ããã€ãã®å°é£ããããŸããTwiMLã®åé¡ã«ã€ããŠã¯ããã§ã«äžã§è¿°ã¹ãŸãããããããSlackã«ã¯ããã®èšäºã§èª¬æããç¹å¥ãªåé¡ããããŸããTL; DRïŒSlackã¯application/x-www-form-urlencoded
ãææ°ã®ã³ãã³ãã§ã¯ãªããã¹ã©ãã·ã¥ã³ãã³ãã®*ã®ã¿* POSTãªã¯ãšã¹ããåãå
¥ããŸãapplication/json
ãããã«ãããSpring Bootã§JSONå
¥åãåŠçããããšãé£ãããªããŸããåºæ¬çãªèãæ¹ã¯ãSOLIDã®ååã«ãããã³ãŒãã®äœæ¥ãšæ¡åŒµãã¯ããã«ç°¡åã«ãªããšããããšã§ããããã§ãSOLIDååã®æŠèŠã¯çµããã§ããéåžžã®åçŽãªJavaã®äŸããã䟿å©ã§ããããšãé¡ã£ãŠããŸãã