ãããŠã倱æãç§ãåŸ ã£ãŠããŸãããããã«ã¡ã¯ãworld-a以å€ã¯äœãæžãããšãã§ããŸããã§ããã ã€ãŸã findãªã©ã®ã³ã³ãœãŒã«ãŠãŒãã£ãªãã£ã®äœææ¹æ³ã倧ãŸãã«æ³åããŠããŸããããIOãšã®æåã®åºäŒãã¯ãã¹ãŠã®ã¢ã€ãã¢ãç Žå£ããŸããã Haskellã«ã¯å€ãã®ã©ã€ãã©ãªãããããã§ããããããã®ããã¥ã¡ã³ãã¯ã»ãšãã©ãããŸããã å žåçãªåé¡ã解決ããäŸãéåžžã«å°ãªãã
çç¶ã¯æ確ã§ããã蚺æã¯ç°¡åã§ããç·Žç¿äžè¶³ã§ãã ãããŠãHaskellã«ãšã£ãŠãããã¯éåžžã«èŠçã§ãã èšèªã¯éåžžã«çããã§ãã ç§ãClojureãããç¥ã£ãŠãããšããäºå®ã§ãããç§ãããŸãå©ããŸããã§ããããªããªã Clojureã¯é¢æ°ã«éç¹ã眮ããŠãããHaskellã¯ãã®åã«çŠç¹ãåœãŠãŠããŸãã
å€ãã®æ°åè ãHaskellã§ã®ç·Žç¿äžè¶³ã®åé¡ã«çŽé¢ããŠãããšæããŸãã ã€ã³ã¿ãŒãã§ã€ã¹ãªãã§å®å šã«äœããæžãããšã¯ãã©ããããããé¢çœããªããåå¿è ã®Haskelistçšã®ãã¹ã¯ããããŸãã¯Webã¢ããªã±ãŒã·ã§ã³ãäœæããããšã¯éåžžã«å°é£ã§ãã ãã®èšäºã§ã¯ãç¹ã«Haskellãç·Žç¿ãããããã©ã®æ¹æ³ã§ã¢ãããŒãããã®ãããããªã人ã®ããã«ãHaskellã§Webã¢ããªã±ãŒã·ã§ã³ãµãŒããŒãäœæããæ¹æ³ã®ç°¡åãªäŸãæäŸããŸãã
æããã£ãã¡ãªäººã®ããã«ïŒãœãŒã¹ã¯ãã¡ãã§ãã
ããã«èšããŸãïŒããã¯å¥ã®Yesodãã¥ãŒããªã¢ã«ã§ã¯ãããŸããã ãã®ãã¬ãŒã ã¯ãŒã¯ã¯ãWebã¢ããªã±ãŒã·ã§ã³ãæ£ããäœæããæ¹æ³ã«é¢ããã¢ã€ãã¢ã決å®ãããã®ã§ãããç§ã¯ãã¹ãŠã«åæããããã§ã¯ãããŸããã ãããã£ãŠãããŒã¹ã¯å°ããªScottyã©ã€ãã©ãªãŒã«ãªãã Warp WebãµãŒããŒçšã®çŸããã«ãŒãèšè¿°æ§æãæäŸããŸãã
ææŠãã
ã·ã³ãã«ãªããã°çšã®Webã¢ããªã±ãŒã·ã§ã³ãµãŒããŒãèšèšããŸãã 次ã®ã«ãŒããå©çšå¯èœã«ãªããŸãã
- GET /èšäº-èšäºã®ãªã¹ãã
- GET / articles /ïŒid-å¥ã®èšäºã
- POST / admin / articles-èšäºãäœæããŸãã
- PUT / admin / articles-èšäºãæŽæ°ããŸãã
- DELETE / admin / articles /ïŒid-èšäºãåé€ããŸãã
ã/ adminãã§å§ãŸããã¹ãŠã®ã«ãŒãã«ã¯ããŠãŒã¶ãŒèªèšŒãå¿ èŠã§ãã ã¹ããŒãã¬ã¹ãµãŒãã¹ã®å Žåã åºæ¬èªèšŒã䜿çšãããšéåžžã«äŸ¿å©ã§ãã åãªã¯ãšã¹ãã«ã¯ããŠãŒã¶ãŒã®ãŠãŒã¶ãŒåãšãã¹ã¯ãŒããå«ãŸããŠããŸãã
äœãå¿ èŠã§ããïŒ
- Haskellã®åºæ¬çãªç¥èãã¢ãããšãã¡ã³ã¯ã¿ãŒã®äžè¬çãªç解ãããã€ã¹èšèšãI / Oãªã©ã
- CabalãŠãŒãã£ãªãã£ããµã³ãããã¯ã¹ã®äœ¿çšãã©ã€ãã©ãªã®æ¥ç¶ããããžã§ã¯ãã®ã³ã³ãã€ã«ããã³å®è¡ã®æ©èœã
- MySQLããã³ããã«é¢ããæãåºæ¬çãªç¥èã
建ç¯
ã¢ãŒããã¯ãã£ãå®è£ ããã«ã¯ã次ã®ã©ã€ãã©ãªã®äœ¿çšãææ¡ããŸãã
- WebãµãŒããŒ-ã¯ãŒãã
- ã«ãŒã¿ãŒ-Scottyã
- ã¢ããªã±ãŒã·ã§ã³æ§æã¯ã³ã³ãã£ã®ã¥ã¬ãŒã¿ãŒã§ãã
- ããŒã¿ããŒã¹ãžã®ã¢ã¯ã»ã¹ïŒmysqlããã³mysql-simpleã
- ããŒã¿ããŒã¹æ¥ç¶ããŒã«ïŒãªãœãŒã¹ããŒã«ã
- ã¯ã©ã€ã¢ã³ããšã®çžäºäœçš-JSONãã©ã€ãã©ãªã䜿çšããREST-aesonã
- åºæ¬èªèšŒã®wai-extraããšã㊠ã¢ããªã±ãŒã·ã§ã³ã¯ã¹ããŒãã¬ã¹ã«ãªããŸãã
ã¢ããªã±ãŒã·ã§ã³ãã¢ãžã¥ãŒã«ã«åå²ããŸãã
- Main.hsã«ã¯ãã¢ããªã±ãŒã·ã§ã³ãã«ãŒã¿ãŒãããã³ã¢ããªã±ãŒã·ã§ã³ã®æ§æãå®è¡ããããã®ã³ãŒããå«ãŸããŸãã
- Db.hs-ããŒã¿ããŒã¹ãžã®ã¢ã¯ã»ã¹ã«é¢é£ãããã¹ãŠã
- View.hs-ããŒã¿è¡šç€ºã
- Domain.hsã®ã¿ã€ããšãµããžã§ã¯ãé åãæäœããããã®é¢æ°ã
- Auth.hs-èªèšŒã®ããã®é¢æ°ã
éãã
ã¢ããªã±ãŒã·ã§ã³çšã®åçŽãªcabalãããžã§ã¯ããäœæããŸãããã
mkdir hblog cd hblog cabal init
ããã§ããã€ãã®è³ªåã«çããå¿ èŠããããŸãããããžã§ã¯ãã®çš®é¡ã¯å®è¡å¯èœãã¡ã€ã«ãéžæããã¡ã€ã³ãã¡ã€ã«-Main.hsããœãŒã¹ãã£ã¬ã¯ããª-srcãéžæããŸãã hblog.cabalãã¡ã€ã«ã®build-dependsã«è¿œå ããå¿ èŠãããã©ã€ãã©ãªã以äžã«ç€ºããŸãã
base >= 4.6 && < 4.7 , scotty >= 0.9.1 , bytestring >= 0.9 && < 0.11 , text >= 0.11 && < 2.0 , mysql >= 0.1.1.8 , mysql-simple >= 0.2.2.5 , aeson >= 0.6 && < 0.9 , HTTP >= 4000.2.19 , transformers >= 0.4.3.0 , wai >= 3.0.2.3 , wai-middleware-static >= 0.7.0.1 , wai-extra >= 3.0.7 , resource-pool >= 0.2.3.2 , configurator >= 0.3.0.0 , MissingH >= 1.3.0.1
ããã§ãã©ã€ãã©ãªã®ããŒãžã§ã³ãšãã®äŸåé¢ä¿ã®æ··ä¹±ãåé¿ããããã«ããµã³ãããã¯ã¹ãäœæããŸãã
cabal sandbox init cabal install âdependencies-only
src / Main.hsãã¡ã€ã«ãå¿ããã«äœæããŠãã ããã
æå°éã®Scotty Webã¢ããªã±ãŒã·ã§ã³ãã©ã®ããã«æ©èœããããèŠãŠã¿ãŸãããã ãã®ãã€ã¯ããã¬ãŒã ã¯ãŒã¯ã®äœ¿çšã«é¢ããããã¥ã¡ã³ããšäŸã¯éåžžã«åªããŠãããããäžç®ã§ãã¹ãŠãæããã«ãªããŸãã ãŸããã·ããã©ãã³ã³ããžã¥ã¬ããŸãã¯ã¹ã«ã©ãã©ã®çµéšãããå Žåã¯ã幞éã ãšèããŠãã ããã ãã®çµéšã¯ããã§å®å šã«åœ¹ç«ã¡ãŸãã
ããã¯ãæå°ã®src / Main.hsã®å€èŠ³ã§ãã
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty import Data.Monoid (mconcat) main = scotty 3000 $ do get "/:word" $ do beam <- param "word" html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
ã³ãŒãã®æåã®è¡ã¯ãåå¿è ãé©ãããå¯èœæ§ããããŸãïŒãªãŒããŒããŒããããè¡ã¯ä»ââã«äœã§ããïŒ ãããã説æããŸãã
ç§ã¯ãä»ã®å€ãã®äººãšåãããã«ãã ããè¯ãå©çã®ããã«HaskellãåŠãŒã ããšã Real World Haskell ããšããæ¬ããHaskellãåŠã³å§ããã®ã§ãããã¹ãåŠçã¯ããã«å€§ããªåé¡ã«ãªããŸããã Haskellã®ããã¹ãã®æäœã«é¢ããæè¯ã®èª¬æã¯ã第10ç« ã®æåã®Haskellã§èŠã€ããŸããã
éåžžã«çãå Žåãå®éã«ã¯3ã€ã®åºæ¬çãªã¿ã€ãã®æååããŒã¿ã䜿çšãããŸãã
- æååã¯æåã®ãªã¹ãã§ãã ãã®ããŒã¿åã¯èšèªã«çµã¿èŸŒãŸããŠããŸãã
- ããã¹ãã¯ãASCIIæåãšUTFæåã®äž¡æ¹ã察象ãšããããŒã¿åã§ãã ããã¹ãã©ã€ãã©ãªã«ãããstrictãšlazyã®2ã€ã®åœ¢åŒã§ååšããŸãã 詳现ã¯ãã¡ã
- ByteString-æååããã€ãã®ã¹ããªãŒã ã«ã·ãªã¢ã«åããããã«èšèšãããŠããŸãã ããã¯ã ãã€ãæååã©ã€ãã©ãªãšãå³å¯ãšé 延ã®2ã€ã®ããŒãžã§ã³ã§æäŸãããŸãã
OverloadedStringsã®èŠåºãã«æ»ããŸãã åé¡ã¯ãããã€ãã®ã¿ã€ãã®æååããŒã¿ãååšããå ŽåããœãŒã¹ã¯T.pack "Hello"ãªã©ã®åŒã³åºãã§ãã£ã±ãã«ãªããããŒã¯ã³ "Hello"ãããã¹ãã«å€æããå¿ èŠãããããšã§ãã ãŸãã¯ãããŒã¯ã³ãByteStringã«å€æããå¿ èŠãããB.packâ Helloâã ãã®æ§æã¬ããŒãžã䜿çšããããã«ãæååããŒã¯ã³ãç®çã®æåååã«ç¬ç«ããŠå€æããOverloadedStringsãã£ã¬ã¯ãã£ãã次ã«ç€ºããŸãã
Main.hsãã¡ã€ã«
äž»ãªæ©èœïŒ
main :: IO () main = do -- application.conf, loadedConf <- C.load [C.Required "application.conf"] dbConf <- makeDbConfig loadedConf case dbConf of Nothing -> putStrLn "No database configuration found, terminating..." Just conf -> do -- ( â 5 , -- 10) pool <- createPool (newConn conf) close 1 5 10 -- Scotty scotty 3000 $ do -- «static» middleware $ staticPolicy (noDots >-> addBase "static") -- . logStdout logStdoutDev middleware $ logStdoutDev -- middleware $ basicAuth (verifyCredentials pool) "Haskell Blog Realm" { authIsProtected = protectedResources } get "/articles" $ do articles <- liftIO $ listArticles pool articlesList articles -- :id get "/articles/:id" $ do id <- param "id" :: ActionM TL.Text maybeArticle <- liftIO $ findArticle pool id viewArticle maybeArticle -- Article Article post "/admin/articles" $ do article <- getArticleParam insertArticle pool article createdArticle article put "/admin/articles" $ do article <- getArticleParam updateArticle pool article updatedArticle article delete "/admin/articles/:id" $ do id <- param "id" :: ActionM TL.Text deleteArticle pool id deletedArticle id
configuratorããã±ãŒãžã䜿çšããŠãã¢ããªã±ãŒã·ã§ã³ãæ§æããŸãã èšå®ãapplication.confãã¡ã€ã«ã«ä¿åãããã®å 容ã以äžã«ç€ºããŸãã
database { name = "hblog" user = "hblog" password = "hblog" }
æ¥ç¶ããŒã«ã«ã¯ããªãœãŒã¹ããŒã«ã©ã€ãã©ãªã䜿çšããŸãã ããŒã¿ããŒã¹ãžã®æ¥ç¶ã¯é«äŸ¡ãªã®ã§ããªã¯ãšã¹ãããšã«äœæããã®ã§ã¯ãªããå€ããã®ãåå©çšããæ©äŒãäžããŠãã ããã createPoolé¢æ°ã®ã¿ã€ãã¯æ¬¡ã®ãšããã§ãã
createPool :: IO a -> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a) createPool create destroy numStripes idleTime maxResources
ããã§ãcreateããã³destroyã¯ããŒã¿ããŒã¹æ¥ç¶ãäœæããã³çµäºããããã®é¢æ°ãnumStripesã¯åå¥ã®æ¥ç¶ãµãããŒã«ã®æ°ãidleTimeã¯æªäœ¿çšã®æ¥ç¶ã®åç¶æéïŒç§ïŒãmaxResourcesã¯ãµãããŒã«å ã®æ倧æ¥ç¶æ°ã§ãã
æ¥ç¶ãéãã«ã¯ãnewConné¢æ°ïŒDb.hsããïŒã䜿çšããŸãã
data DbConfig = DbConfig { dbName :: String, dbUser :: String, dbPassword :: String } deriving (Show, Generic) newConn :: DbConfig -> IO Connection newConn conf = connect defaultConnectInfo { connectUser = dbUser conf , connectPassword = dbPassword conf , connectDatabase = dbName conf }
DbConfigèªäœã¯æ¬¡ã®ããã«äœæãããŸãã
makeDbConfig :: C.Config -> IO (Maybe Db.DbConfig) makeDbConfig conf = do name <- C.lookup conf "database.name" :: IO (Maybe String) user <- C.lookup conf "database.user" :: IO (Maybe String) password <- C.lookup conf "database.password" :: IO (Maybe String) return $ DbConfig <$> name <*> user <*> password
å ¥åã«ã¯ãapplication.confããèªã¿åã£ãŠè§£æããData.Configurator.Configãæž¡ãããåºåã¯IOã·ã§ã«ã«å²ãŸããDbConfigã§ããå¯èœæ§ããããŸãã
åå¿è åãã®ãã®ãããªãšã³ããªã¯å°ããããã«ãããããããŸããããããã§äœãèµ·ãã£ãŠããã®ã説æããããšæããŸãã
åŒã¿ã€ãC.lookup conf "database.name"ã¯ãããããIOã§å²ãŸããæååã§ãã 次ã®ããã«IOããæœåºã§ããŸãã
name <- C.lookup conf "database.name" :: IO (Maybe String)
ãããã£ãŠãå®æ°åããŠãŒã¶ãŒããã¹ã¯ãŒãã®çš®é¡ã¯å€åæååã§ãã
DbConfigããŒã¿ã³ã³ã¹ãã©ã¯ã¿ãŒã®ã¿ã€ãã¯æ¬¡ã®ãšããã§ãã
DbConfig :: String -> String -> String -> DbConfig
ãã®é¢æ°ã¯3è¡ã®å ¥åãåãåããDbConfigãè¿ããŸãã
é¢æ°ã®ã¿ã€ãïŒ<$>ïŒã¯æ¬¡ã®ãšããã§ãã
(<$>) :: Functor f => (a -> b) -> fa -> fb
ã€ãŸã éåžžã®é¢æ°ããã¡ã³ã¯ã¿ãŒãåãããã®å€ã«é©çšãããé¢æ°ãæã€ãã¡ã³ã¯ã¿ãŒãè¿ããŸãã èŠããã«ãããã¯éåžžã®ãããã§ãã
DbConfig <$> nameãšã³ããªã¯ãååããæååãååŸãïŒåã®ã¿ã€ãã¯Maybe StringïŒãDbConfigã³ã³ã¹ãã©ã¯ã¿ãŒã®æåã®ãã©ã¡ãŒã¿ãŒã«å€ãå²ãåœãŠãMaybeã·ã§ã«ã§ã«ãªãŒåãããDbConfigãè¿ããŸãã
DbConfig <$> name :: Maybe (String -> String -> DbConfig)
ããã§ã¯ããã§ã«1ã€ã®æååã®è»¢éãå°ãªããªã£ãŠããããšã«æ³šæããŠãã ããã
ã¿ã€ãïŒ<*>ïŒã¯<$>ã«äŒŒãŠããŸãïŒ
(<*>) :: Applicative f => f (a -> b) -> fa -> fb
圌ã¯å€ãé¢æ°ã§ãããã¡ã³ã¯ã¿ãŒãåããå¥ã®ãã¡ã³ã¯ã¿ãŒãåããæåã®ãã¡ã³ã¯ã¿ãŒããã®é¢æ°ã2çªç®ã®ãã¡ã³ã¯ã¿ãŒããå€ã«é©çšããæ°ãããã¡ã³ã¯ã¿ãŒãè¿ããŸãã
ãããã£ãŠããšã³ããªDbConfig <$> name <*> userã®ã¿ã€ãã¯æ¬¡ã®ãšããã§ãã
DbConfig <$> name <*> user :: Maybe (String -> DbConfig)
ãã¹ã¯ãŒããå ¥åããæåŸã®æååãã©ã¡ãŒã¿ãŒãæ®ã£ãŠããŸãã
DbConfig <$> name <*> user <*> password :: Maybe DbConfig
èªèšŒ
ã¡ã€ã³é¢æ°ã§ã¯ãæåŸã®è€éãªæ§é ãæ®ããŸãã-ããã¯ããã«ãŠã§ã¢basicAuthã§ãã basicAuthé¢æ°ã®ã¿ã€ãã¯æ¬¡ã®ãšããã§ãã
basicAuth :: CheckCreds -> AuthSettings -> Middleware
æåã®ãã©ã¡ãŒã¿ãŒã¯ããŒã¿ããŒã¹å ã®ãŠãŒã¶ãŒã®ååšããã§ãã¯ããé¢æ°ã§ã2çªç®ã¯èªèšŒä¿è·ãå¿ èŠãªã«ãŒãã決å®ããŸãã ãããã®ã¿ã€ãïŒ
type CheckCreds = ByteString -> ByteString -> ResourceT IO Bool data AuthSettings = AuthSettings { authRealm :: !ByteString , authOnNoAuth :: !(ByteString -> Application) , authIsProtected :: !(Request -> ResourceT IO Bool) }
AuthSettingsããŒã¿åã¯éåžžã«è€éã§ãããããå°ã詳ããç¥ãããå Žåã¯ããã¡ãã®ãœãŒã¹ãåç §ããŠãã ãã ã ããã§ã¯ãauthIsProtectedãšãã1ã€ã®ãã©ã¡ãŒã¿ãŒã®ã¿ã«é¢å¿ããããŸãã ããã¯ããªã¯ãšã¹ãã«ãããèªèšŒãèŠæ±ãããã©ããã決å®ã§ããé¢æ°ã§ãã ããã°ã®å®è£ ã¯æ¬¡ã®ãšããã§ãã
protectedResources :: Request -> IO Bool protectedResources request = do let path = pathInfo request return $ protect path where protect (p : _) = p == "admin" protect _ = False
pathInfoé¢æ°ã«ã¯æ¬¡ã®ã¿ã€ãããããŸãã
pathInfo :: Request -> [Text]
ãªã¯ãšã¹ããåãåãããªã¯ãšã¹ãã«ãŒããåºåãæåã/ãã§éšåæååã«åå²ããåŸã«ååŸããæååã®ãªã¹ããè¿ããŸãã
ãããã£ãŠããªã¯ãšã¹ããã/ adminãã§å§ãŸãå ŽåãprotectedResourcesé¢æ°ã¯IO Trueãè¿ããèªèšŒãèŠæ±ããŸãã
ãã ãããŠãŒã¶ãŒãšãã¹ã¯ãŒãã確èªããverifyCredentialsé¢æ°ã¯ãããŒã¿ããŒã¹ãšã®çžäºäœçšãåç §ããããã以äžã®ãšããã§ãã
ããŒã¿ããŒã¹ã®çžäºäœçš
æ¥ç¶ããŒã«ã䜿çšããŠããŒã¿ããŒã¹ããããŒã¿ãæœåºãããŠãŒãã£ãªãã£é¢æ°ïŒ
fetchSimple :: QueryResults r => Pool M.Connection -> Query -> IO [r] fetchSimple pool sql = withResource pool retrieve where retrieve conn = query_ conn sql fetch :: (QueryResults r, QueryParams q) => Pool M.Connection -> q -> Query -> IO [r] fetch pool args sql = withResource pool retrieve where retrieve conn = query conn sql args
ãã©ã¡ãŒã¿ãªãã®ã¯ãšãªã«ã¯fetchSimpleé¢æ°ã䜿çšãããã©ã¡ãŒã¿ä»ãã®ã¯ãšãªã«ã¯fetchSimpleé¢æ°ã䜿çšããå¿ èŠããããŸãã ããŒã¿ã®å€æŽã¯ãexecSqlé¢æ°ã䜿çšããŠå®è¡ã§ããŸãã
execSql :: QueryParams q => Pool M.Connection -> q -> Query -> IO Int64 execSql pool args sql = withResource pool ins where ins conn = execute conn sql args
ãã©ã³ã¶ã¯ã·ã§ã³ã䜿çšããå¿ èŠãããå ŽåãexecSqlTé¢æ°ã次ã«ç€ºããŸãã
execSqlT :: QueryParams q => Pool M.Connection -> q -> Query -> IO Int64 execSqlT pool args sql = withResource pool ins where ins conn = withTransaction conn $ execute conn sql args
ããšãã°ããã§ããé¢æ°ã䜿çšãããšããã°ã€ã³ã«ãã£ãŠããŒã¿ããŒã¹å ã®ãŠãŒã¶ãŒã®ãã¹ã¯ãŒãã®ããã·ã¥ãæ€çŽ¢ã§ããŸãã
findUserByLogin :: Pool Connection -> String -> IO (Maybe String) findUserByLogin pool login = do res <- liftIO $ fetch pool (Only login) "SELECT * FROM user WHERE login=?" :: IO [(Integer, String, String)] return $ password res where password [(_, _, pwd)] = Just pwd password _ = Nothing
Auth.hsã¢ãžã¥ãŒã«ã§å¿ èŠã§ãïŒ
verifyCredentials :: Pool Connection -> B.ByteString -> B.ByteString -> IO Bool verifyCredentials pool user password = do pwd <- findUserByLogin pool (BC.unpack user) return $ comparePasswords pwd (BC.unpack password) where comparePasswords Nothing _ = False comparePasswords (Just p) password = p == (md5s $ Str password)
ã芧ã®ãšããããã¹ã¯ãŒãããã·ã¥ãããŒã¿ããŒã¹ã§èŠã€ãã£ãå Žåãmd5ã¢ã«ãŽãªãºã ã䜿çšããŠãšã³ã³ãŒãããããªã¯ãšã¹ããããã¹ã¯ãŒãã«ãããã³ã°ã§ããŸãã
ãã ããããŒã¿ããŒã¹ã«ã¯ãŠãŒã¶ãŒã ãã§ãªããããã°ãäœæãç·šéã衚瀺ã§ããèšäºãä¿åãããŸãã Domain.hsãã¡ã€ã«ã§ãid title bodyTextãã£ãŒã«ãã䜿çšããŠArticleããŒã¿åãå®çŸ©ããŸãã
data Article = Article Integer Text Text deriving (Show)
ããã§ããã®ã¿ã€ãã®ããŒã¿ããŒã¹ã«CRUDé¢æ°ãå®çŸ©ã§ããŸãã
listArticles :: Pool Connection -> IO [Article] listArticles pool = do res <- fetchSimple pool "SELECT * FROM article ORDER BY id DESC" :: IO [(Integer, TL.Text, TL.Text)] return $ map (\(id, title, bodyText) -> Article id title bodyText) res findArticle :: Pool Connection -> TL.Text -> IO (Maybe Article) findArticle pool id = do res <- fetch pool (Only id) "SELECT * FROM article WHERE id=?" :: IO [(Integer, TL.Text, TL.Text)] return $ oneArticle res where oneArticle ((id, title, bodyText) : _) = Just $ Article id title bodyText oneArticle _ = Nothing insertArticle :: Pool Connection -> Maybe Article -> ActionT TL.Text IO () insertArticle pool Nothing = return () insertArticle pool (Just (Article id title bodyText)) = do liftIO $ execSqlT pool [title, bodyText] "INSERT INTO article(title, bodyText) VALUES(?,?)" return () updateArticle :: Pool Connection -> Maybe Article -> ActionT TL.Text IO () updateArticle pool Nothing = return () updateArticle pool (Just (Article id title bodyText)) = do liftIO $ execSqlT pool [title, bodyText, (TL.decodeUtf8 $ BL.pack $ show id)] "UPDATE article SET title=?, bodyText=? WHERE id=?" return () deleteArticle :: Pool Connection -> TL.Text -> ActionT TL.Text IO () deleteArticle pool id = do liftIO $ execSqlT pool [id] "DELETE FROM article WHERE id=?" return ()
ããã§æãéèŠãªã®ã¯ãinsertArticleããã³updateArticleé¢æ°ã§ãã å ¥åãšããŠMaybe Articleãåãå ¥ããããŒã¿ããŒã¹å ã®å¯Ÿå¿ãããšã³ããªãæ¿å ¥/æŽæ°ããŸãã ãããããã®å€åèšäºã¯ã©ãã§å ¥æã§ããŸããïŒ
ãã¹ãŠãåçŽã§ããŠãŒã¶ãŒã¯JSONã§ãšã³ã³ãŒããããArticleãPUTãŸãã¯POSTãªã¯ãšã¹ãã®æ¬æã§æž¡ãå¿ èŠããããŸãã JSONå å€ã®èšäºããšã³ã³ãŒãããã³ãã³ãŒãããããã®é¢æ°ã¯æ¬¡ã®ãšããã§ãã
instance FromJSON Article where parseJSON (Object v) = Article <$> v .:? "id" .!= 0 <*> v .: "title" <*> v .: "bodyText" instance ToJSON Article where toJSON (Article id title bodyText) = object ["id" .= id, "title" .= title, "bodyText" .= bodyText]
JSONãåŠçããã«ã¯ãaesonã©ã€ãã©ãªã䜿çšããŸã ã詳现ã«ã€ããŠã¯ã ãã¡ããã芧ãã ãã ã
ã芧ã®ãšããããã³ãŒãæã«ã¯idãã£ãŒã«ãã¯ãªãã·ã§ã³ã§ãããJSONã®è¡ã«ãªãå Žåãããã©ã«ãå€ã¯0ã«èšå®ãããŸããArticleãšã³ããªã®äœææã«ã¯idãã£ãŒã«ãã¯ååšããŸããã idã¯ããŒã¿ããŒã¹èªäœãäœæããå¿ èŠããããŸãã ãã ããidã¯æŽæ°ãªã¯ãšã¹ãã«å«ãŸããŸãã
ããŒã¿æ瀺
Main.hsãã¡ã€ã«ã«æ»ãããªã¯ãšã¹ããã©ã¡ãŒã¿ãååŸããæ¹æ³ãèŠãŠã¿ãŸãããã paramé¢æ°ã䜿çšããŠãã«ãŒããããã©ã¡ãŒã¿ãŒãååŸã§ããŸãã
param :: Parsable a => TL.Text -> ActionM a
ãããŠããªã¯ãšã¹ãé¢æ°ã¯bodyé¢æ°ã§ååŸã§ããŸãïŒ
body :: ActionM Data.ByteString.Lazy.Internal.ByteString
ãªã¯ãšã¹ãã®æ¬æãååŸããŠè§£æããMaybeãè¿ãããšãã§ããé¢æ°ã以äžã«ç€ºããŸã
getArticleParam :: ActionT TL.Text IO (Maybe Article) getArticleParam = do b <- body return $ (decode b :: Maybe Article) where makeArticle s = ""
æåŸã«æ®ã£ãã®ã¯ãã¯ã©ã€ã¢ã³ãã«ããŒã¿ãè¿ãããšã§ãã ãããè¡ãã«ã¯ãViews.hsãã¡ã€ã«ã§æ¬¡ã®é¢æ°ãå®çŸ©ããŸãã
articlesList :: [Article] -> ActionM () articlesList articles = json articles viewArticle :: Maybe Article -> ActionM () viewArticle Nothing = json () viewArticle (Just article) = json article createdArticle :: Maybe Article -> ActionM () createdArticle article = json () updatedArticle :: Maybe Article -> ActionM () updatedArticle article = json () deletedArticle :: TL.Text -> ActionM () deletedArticle id = json ()
ãµãŒããŒã®ããã©ãŒãã³ã¹
ãã¹ãã«ã¯ã8GBã®ã¡ã¢ãªãšã¯ã¢ããã³ã¢Intel Core i7ãæèŒããSamsung 700Zã©ãããããã䜿çšããŸããã
- èšäºãšã³ããªãäœæããããã®1000åã®é£ç¶PUTèŠæ±ã
å¹³åå¿çæéïŒ40ããªç§ã1ç§ãããçŽ25ãªã¯ãšã¹ãã§ãã
- ãããã100 PUTãªã¯ãšã¹ããå«ã100ã¹ã¬ããã
å¹³åå¿çæéïŒ1248ããªç§ã1ç§ãããçŽ80ã®åæèŠæ±ã
- 1000åã®GETãªã¯ãšã¹ããå«ã100åã®ã¹ã¬ããã10åã®èšäºãšã³ããªãè¿ããŸãã
å¹³åå¿çæéïŒ165ããªç§ã1ç§ãããçŽ600ãªã¯ãšã¹ãã
å°ãªããšãäœããšæ¯èŒã§ããããã«ããããã«ãJava 7ãšSpring 4ã§Tomcat 7 WebãµãŒããŒã䜿çšããŠãŸã£ããåããµãŒããŒãå®è£ ãã次ã®æ°åãåãåããŸããã
- èšäºãšã³ããªãäœæããããã®1000åã®é£ç¶PUTèŠæ±ã
å¹³åå¿çæéïŒ51ããªç§ã1ç§ãããçŽ19ã20ãªã¯ãšã¹ãã§ãã
- ãããã100åã®PUTèŠæ±ãå«ã100åã®ã¹ã¬ããã
å¹³åå¿çæéïŒ104ããªç§ã1ç§ãããçŽ960ã®åæèŠæ±ã
- 1000åã®GETãªã¯ãšã¹ããå«ã100åã®ã¹ã¬ããã10åã®èšäºãšã³ããªãè¿ããŸãã
å¹³åå¿çæéïŒ26ããªç§ã1ç§ãããçŽ3800ãªã¯ãšã¹ãã
çµè«
Haskellã®ç·Žç¿ãäžè¶³ããŠããŠããã®äžã§Webã¢ããªã±ãŒã·ã§ã³ãäœæããããšããå Žåãèšäº-èšäºã§èª¬æãããŠãã1ã€ã®ãšã³ãã£ãã£ã«å¯ŸããCRUDæäœã䜿çšããåçŽãªãµãŒããŒã®äŸãèŠã€ããããšãã§ããŸãã ã¢ããªã±ãŒã·ã§ã³ã¯JSON RESTãµãŒãã¹ãšããŠå®è£ ãããå®å šãªã«ãŒãã§ã®åºæ¬èªèšŒãå¿ èŠã§ãã MySQL DBMSã¯ããŒã¿ã¹ãã¬ãŒãžã«äœ¿çšãããæ¥ç¶ããŒã«ã¯ããã©ãŒãã³ã¹ãæ¹åããããã«äœ¿çšãããŸãã ã¢ããªã±ãŒã·ã§ã³ã¯ã»ãã·ã§ã³ã«ç¶æ ãä¿åããªããããæ°Žå¹³æ¹åãžã®ã¹ã±ãŒãªã³ã°ã¯éåžžã«ç°¡åã§ããããã«ãã¹ããŒãã¬ã¹ãµãŒããŒã¯ãã€ã¯ããµãŒãã¹ ã¢ãŒããã¯ãã£ã®éçºã«æé©ã§ãã
Haskellã䜿çšããŠJSON RESTãµãŒããŒãéçºããããšã§ãçããŠçŸãããœãŒã¹ãååŸã§ããŸãããããã¯ããšãããããªãã¡ã¯ã¿ãªã³ã°ãå€æŽãè¿œå ã«å€ãã®äœæ¥ãå¿ èŠãšããªããããä¿å®ã容æã§ãã ã³ã³ãã€ã©ã¯ããã¹ãŠã®å€æŽã®æ£ç¢ºæ§ããã§ãã¯ããŸãã Haskellã䜿çšããããšã®æ¬ ç¹ã¯ãJavaã§äœæãããåæ§ã®ãµãŒãã¹ãšæ¯èŒããŠãçµæã®WebãµãŒãã¹ã®ããã©ãŒãã³ã¹ãããã»ã©é«ããªãããšã§ãã
PS
ã³ã¡ã³ãã®ã¢ããã€ã¹ã«åºã¥ããŠãè¿œå ã®ãã¹ããå®æœããŸããã ã¹ã¬ããæ°ãN = 8ã«å€æŽããŠããããã©ãŒãã³ã¹ã«ã¯åœ±é¿ããŸããã Nãããã«æžå°ãããšãããã©ãŒãã³ã¹ãäœäžããŸãã ç§ã®ã©ãããããã«ã¯8ã€ã®è«çã³ã¢ããããŸãã
å¥ã®èå³æ·±ãããšã ããŒã¿ããŒã¹ãžã®ã¬ã³ãŒãã®ä¿åãç¡å¹ã«ãããšãHaskellã«å¯ŸãããµãŒãã¹ã®å¿çã®å¹³åé 延ã¯6ããªç§ïŒïŒïŒã«äœäžããŸããJavaã®åæ§ã®ãµãŒãã¹ã§ã¯ãä»åã¯80ããªç§ã§ãã ã€ãŸã 瀺ãããŠãããããžã§ã¯ãã®ããã«ããã¯ã¯ããŒã¿ããŒã¹ãšã®çžäºäœçšã§ããããããªãã«ãããšãHaskellã¯Javaã®åæ§ã®æ©èœããã13åé«éã«ãªããŸãã ã¡ã¢ãªæ¶è²»éãæ°åäœããªããŸãã400MBã«å¯ŸããŠçŽ80MBã§ãã