
node.jsãšã¯é¢ä¿ã®ãªãæªç¥ã®éçŽã ããããhabrã§ã¯ãåçãæ·»ä»ããã®ãè¯ã圢ãšèããããŠããŸã
å°ãåã«ãnode.jsã§* SQLãªã©ã®ãªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ãMongoãªã©ã®noSQLã䜿çšããã®ã¯è€éã§ããªãããã°ã©ããŒã®é床ã«åããã代æ¿ãœãªã¥ãŒã·ã§ã³ãäœæããã®ãïŒé床ã«åãããåŸæ¥ã®ãœãªã¥ãŒã·ã§ã³ãšæ¯ã¹ãŠïŒããŒã¿ããŒã¹ãæäœããïŒããã³æå°ãšã³ããªãããå€ã®APIã®åçŽããšã³ã³ãã¯ããã æåã®ã€ã³ã¹ãã¬ãŒã·ã§ã³ã®æºã¯ãã¬ããŒããminimal surface APIã ã2çªç®-ããã«ãã¯ãŒã«ã®æåãªåŒçš
ããã°ã©ããŒã¯ãã¢ããªã±ãŒã·ã§ã³ã®éèŠã§ã¯ãªãéšåã®é床ãå¿é ããç°åžžãªæéãè²»ãããå¹çãé«ãããããã®è©Šã¿ã¯ããããã®ã¢ããªã±ãŒã·ã§ã³ã®ãããã°ãšãµããŒãã«æ·±å»ãªåœ±é¿ãåãŒããŸãã ææå°æ©ã®æé©åã¯ãã¹ãŠã®æªã®æ ¹æºã§ã ã
Dixleimer 1ïŒããã§èª¬æããã©ã€ãã©ãªã¯åæããŒã¿çã§ãã ä»ã®ãšãããããªãã®äººçã®åæ¥çãŸãã¯éèŠãªãããžã§ã¯ãã«ã¯äœ¿çšããªãã§ãã ããã
å 責äºé 2ïŒããã¹ãã«ã¯è€éãªçšèªã衚瀺ãããã³ãŒãäŸã«ã¯å€ãã®ES6æ©èœããããŸãã äœããç解ã§ããªããšæãããå Žå-ã³ã¡ã³ããæžããŠãã ãããç§ã¯ããã¹ããç°¡çŽ åããã³ãŒãã«ã³ã¡ã³ããè¿œå ããããšããŸã
sequelizeã䜿çšãããšãïŒä»ã®ã©ã€ãã©ãªã®æ¹ãè¯ããšã¯æããªãã§ãã ããïŒãã©ãããŠãããè€éãªã®ããšæããæããŸããã ãããå éšããã©ã®ããã«æ©èœããããç解ãããšãã芳ç¹ã§ã¯ãªãããã-ç§ã¯ã©ã€ãã©ãªã€ã³ã¿ãŒãã§ãŒã¹ãæãäžããŸãã ããããæã䌞ã³ãªãããéçºè ãç§ã®ããã§ã¯ãªãDBAã«çŒãä»ããŠãããã®ã©ã¡ããã§ãã
ä»ãç§ã¯åœŒããããŒã«ããã¯ã¹ã«è¿œå ããããšããããšãäœãã§ããããç¥ã£ãŠããŸãã åºåã¯ãåã®14ãçµã¿åããã15çªç®ã®æšæºã§ããå°çã®ãããªãžã£ã°ãªã³ã°ã®çµã¿åããã¯ãMySQLãSQlite3ãPostgresãCouchDBãMongoãRedisãNeo4jãªã©ã®éåžžã«å€æ§ãªããŒã¿ããŒã¹ã®ãªã¹ãã§äœ¿çšã§ããããšã瀺ããŠããŸãã
ããããå°èŠæš¡ãªãããžã§ã¯ã-ããããçš®é¡ã®ãã¬ã°ã©ã ããããéçºãµãŒããŒãããã³SPA-ã®å Žåãè€éãªORMã¢ãããŒãã®è£ã«ããæ©èœã®è€éãªéšåã¯å¿ èŠãããŸããã§ããã åºæ¬çã«å¿ èŠãªæ©èœã¯ãã¬ã³ãŒããæ¡ä»¶ããã³é¢ä¿ã«ããéžæã®ä¿åãšæ€çŽ¢ã§ãã ææå°æ©ãªæé©åã¯å¿ èŠãããŸãããããŒã¿ããŒã¹ãããã£ãŒã«ãã®äžéšãååŸããããã¯ãšãªã®æé©åãè¡ã£ãããé¢æ°ãä¿åãããããŸãã ãªã¬ãŒã·ã§ã³ã®éžæïŒãªããžã§ã¯ããšãã¹ãŠã®æ¥ç¶ãååŸããïŒã¯ããã©ã³ã¶ã¯ã·ã§ã³ã«ãã£ãŠåœ¢åŒåã§ããŸãã é床ãäœäžããããã宣èšæ§æã®è¿œå ãšã³ãã£ãã£ã®æããªããªããŸãã æãçŽç²ãªåœ¢ã®ãªãã«ã ã®ã«ããœãªã
åæ çãªäœè«ïŒæ°äžäººããæ°åäžäººã®ãŠãŒã¶ãŒããããããžã§ã¯ãã®éçºã®æŽå²ãèŠããšãäžå®ã®æéãçµéãããšãéçºè ã¯ã¹ããŒãã倱ããŸãã ã¯ãšãªãããŒã¿ããŒã¹ãèšèªããã©ãããã©ãŒã ãå€æŽããŠæ©èœããããã«ããŸãã ãããžã§ã¯ããæ®åœ±ãããå Žåã圌ã¯éšåã亀æããå¿ èŠããããããŒã¹ã§æåã«äœæ¥ããã®ã¯ãæåã®åªåããå°æ¥ã®ããã«ãè²»ããããŠããªãå Žåã§ãã åæã«ãéããŠè€éãªORMæ§æã¯çœ®æãè€éã«ããŸãã çµè«ã¯æããã§ããORMã®éžæãå°æ¥ã®éæè¡çãªè² åµãšããŠè©äŸ¡ããå Žåãå¹çã®äœããœãªã¥ãŒã·ã§ã³ãéçºè ã®é床ãæäŸããæå°éã®APIãæäŸããããšãæ£ããéžæã§ãããå¥ã®ãœãªã¥ãŒã·ã§ã³ãžã®ç§»è¡ãç°¡çŽ åããŸãã
ãŠããããäœããŸãã
DBã§ã¯ãªããJSäžå¿ã®ActiveRecord-ãã ããäžéšã®å Žæã§ã¯ãå€å žçãªãã¿ãŒã³ããé ããããŸããã
ããŒã¿ããŒã¹äžå¿ã§ã¯ãªããããããŒã¿ããŒã¹ã¯ãœãªã¥ãŒã·ã§ã³ã®èŠä»¶ã«åŸã£ãŠéžæããããã®ã§ãããç¹å®ã®ããŒã¿ããŒã¹ã«ã€ããŠæ±ºå®ããããã®ã§ã¯ãªãããšãç解ããããšãéèŠã§ãã Neo4jããªããžããªãšããŠéžæãããŸããã ãã®ãœãªã¥ãŒã·ã§ã³ã«ã¯é·æãšçæããããŸããããããŸã§ã®ãšããããã«å©ç¹ããããŸãã
neo4jã«æ £ããŠããªãå Žå-ããã¯ãSQLã䜿ããããWebã¯ã©ã€ã¢ã³ããããã¯ã¹ããã®ãã«ããã¹ãã€ã³ããã¯ã¹ïŒluceneã䜿çšïŒãPostgresã«æ¯ã¹ãŠããäœãïŒç·åœ¢ïŒããã©ãŒãã³ã¹ãããåå¿è ã«ãšã£ãŠã¯ããã«ç解ããããèšèªãåãã人æ°ã®ããã°ã©ãããŒã¿ããŒã¹ã§ã/ Mysqlã ãã¹ãŠã®ã€ã³ã¹ããŒã«æé ã¯ã http ïŒ //neo4j.com/download-thanks/ïŒedition = communityã«ãããŸãã Macã§ã¯ãbrew install neo4jãä»ããŠã€ã³ã¹ããŒã«ãããŸã
ç°¡åãªæ¥ç¶ãšãšã³ããªããå§ããŸãããã
const {Connection, Record} = require('agregate') const dbPath = 'http://neo4j:password@localhost:7474'; class ConnectedRecord extends Record { static connection = new Connection(dbPath); } class User extends ConnectedRecord {} User.register() // , const user = new User({name: 'foo'}) user.surname = 'bar' user.save() .then(() => User.where({name: 'foo'})) .then(([user]) => console.log(user)) //=> User {name: 'foo', surname: 'bar'}
ã³ãŒãã®ããããããããæãåºãå¯äžã®ãã®ã¯ãUser.registerïŒïŒã®åŒã³åºãã§ãã JSã§ã¯ããã³ãã©ãŒããã³ã°ã¢ããããŠã¯ã©ã¹ãäœæããããšã¯ã§ããŸããïŒãããŠããã®ããšã«ã€ããŠèšèªã®éçºè ã«æè¬ããŸãïŒã
Record.registerã¡ãœããã¯æ¬¡ã®3ã€ã®ããšãè¡ããŸãã
- ãã®ã¯ã©ã¹ãæ¢åã®ããŒã¿ããŒã¹æ¥ç¶ã«ç»é²ããŸãã åã«çœ®ã-æ¥ç¶å
ã®ãããã§ãé¢é£ä»ããã©ãã«ã-ãã¯ã©ã¹ããæšåãããŸãã ïŒåŸã§ãããã«ã€ããŠïŒé¢é£ä»ãã解決ãããšããããŒã¿ããŒã¹ãªããžã§ã¯ããJSãªããžã§ã¯ãã«å€æããããã«äœ¿çšãããã®ã¯ãã®ãããã§ã
- å
éšã©ã€ãã©ãªããã»ã¹ãéå§ããŸãïŒã»ãã¥ãªãã£ã®ããã«ãã€ã³ããã¯ã¹ä»ããšuuidã®äžææ§ã®å¶éïŒ
- ã«ã¹ã¿ã ã€ã³ããã¯ã¹ã®ã€ã³ããã¯ã¹äœæãéå§ããŸãïŒãã®ã¯ã©ã¹ã«èšå®ãããŠããå ŽåïŒã
æœè±¡ã¯ã©ã¹ã®å Žåããã®ã¡ãœãããåŒã³åºãå¿ èŠã¯ãããŸããã
ES2015ã§ã¯ãéçããããã£ã¯ãšã³ãã£ãã£ããããã£ãšåãæ¹æ³ã§ç¶æ¿ãããŸã-瀺ãããŠããããã«ã芪ã¯ã©ã¹ã§äžåºŠconnectonã宣èšãããŸãã ããŒã¿ããŒã¹ã1ã€ãããªãå ŽåãRecord.connectionãæ¥ç¶ã«å²ãåœãŠãããšãã§ããŸãããããã¯éçºã®èŠ³ç¹ããã¯æ£ãããããŸããã
é¢ä¿ãšé¢ä¿
äŸãè€éã«ããŸãããã ACLãå®è¡ããŠããŠãé¢ä¿ãå¿ èŠã ãšæ³åããŠãã ããã
const {Connection, Record, Relation} = require('agregate'); // Role Permission const Role = require('./role'); const Permission = require('./permission'); export default class User extends ConnectedRecord { roles = new Relation(this, 'has_role', {target: Role}); permissions = new Relation(this.roles, 'has_permission', {target: Permission}); hasPermission = ::this.permissions.has }
ããèŠãŠããªããšãå®éã«ã¯this.permissionsãå€å¯Ÿå€ã®é¢ä¿ã§ããããšãããã«ããããŸããã ãã®çš®ã®æ§æã䜿çšãããšãæ€çŽ¢ãåé€ãå¯çšæ§ã®ç¢ºèªãªã©ãå®å šãªã¯ãšãªã䜿çšã§ããé¢ä¿ã®é·ããã§ãŒã³ãæ§ç¯ã§ããŸãã
Relationã¯ãES6ã®çµã¿èŸŒã¿Setãªããžã§ã¯ãããšãã¥ã¬ãŒãããŸãã APIã¯ç°ãªããŸãããããç¥ãããŠãããããã«ç解ãããŸãã éãã¯ãã¡ãœããã¯æ¢ã«ããŒã¿ãè¿ãPromiseãè¿ããsizeïŒïŒã¯ããããã£ã§ã¯ãªãã¡ãœããã§ããããšã§ãã ããã«ãéä¿¡ãããèŠçŽ ã®é åãšãªã¬ãŒã·ã§ã³ã«å«ãŸããèŠçŽ ã®å ±ééšåãè¿ã#intersectã¡ãœãããšã以äžã®ããã«æããã«ãã#whereã¡ãœããããããŸããã
DBã§æ€çŽ¢
ããã«ã¯ãåãAPIãæã€ã¡ãœããã䜿çšã§ããŸããRecord.whereïŒïŒã¯ã©ã¹ã®ã¡ãœãããšãRelationïŒwhereïŒïŒã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ã¡ãœããã§ãã 䜿çšå¯èœãªãªãã»ãããå¶éãé åºãå€ã«ããæ€çŽ¢ãé åã®å 容ãé åãžã®ãšã³ããªïŒã¯ããåä»ãé åã¯neo4jã®ããªããã£ãã®1ã€ã§ãïŒããã³ãµãã¹ããªã³ã°ã æ€çŽ¢ã«ã¯å€ãã®æ©äŒããããŸãã ãããã¯ãã¹ãŠã®äž»èŠãªã¿ã¹ã¯ãã«ããŒããŠããŸãã ãã¹ãŠã®ãªãã·ã§ã³ãåæããã®ã¯éåžžã«é£ãããããtypescriptã®ãããªæ§æã§æ£åŒãªèª¬æãèŠãã®ã¯ç°¡åã§ãã
var dbPrimitiveType = bool | string | number | Array<bool> | Array<string> | Array<number> async function where( params?: { [string: queryKey]: dbPrimitiveType | { $gt?: number //greater than - $gte?: number //greater than or equal - $lt?: number //less than - $lte?: number //less than or equal - $exists?: bool // $startsWith?: Array<string> | string // $endsWith?: Array<string> | string // $contains?: Array<string> | string // $has?: Array<dbPrimitiveType> | dbPrimitiveType // $in?: Array<dbPrimitiveType> | dbPrimitiveType // } }, opts?: { order?: string | Array<string>; // - key key DESC key ASC, ['created_at', 'friends DESC'] offset?: number; limit?: number; }, transaction?: Queryable): Array<Record>
ååŒ
äžèšã®APIã䜿çšãããšäœæ¥ã§ããŸãã æ®ã£ãŠããã®ã¯ååæ§ã®åé¡ã ãã§ãããããã¯ãã©ã³ã¶ã¯ã·ã§ã³ã䜿çšããŠå€å žçã«è§£æ±ºãããŸãã
éçŽã§ã¯ã2ã€ã®æ¹æ³ã§ãã©ã³ã¶ã¯ã·ã§ã³ãæäœã§ããŸã-åçŽãŸãã¯ç°¡åã§ãã
æ確ãªæ¹æ³ã¯ãé¡ã®ãã©ã³ã¶ã¯ã·ã§ã³ã䜿çšããããšã§ãã ãããè¡ãã«ã¯ãïŒç¹ã«ïŒæåŸã®åŒæ°ãæž¡ããŸãã ããŒã¿ããŒã¹ã§æ©èœãããã¹ãŠã®æšæºã¡ãœããã¯ããã®è¡šèšæ³ããµããŒãããŠããŸãã
class Post extends Record { author = ::new Relation(this, 'created', {direction: -1}).only // only, . , . async static createAndAssign(text, user) { const transaction = this.connection.transaction() const post = await new this({text}).save(transaction) await post.author(user, transaction) await transaction.commit() return post } // , - async static createAndAssign(text, user) { const transaction = this.connection.transaction() try { const post = await new this({text}).save(transaction) await post.author(user, transaction) await transaction.commit() return post } catch (e) { await transaction.rollback() throw e } } }
æ¥ç¶ãªããžã§ã¯ãïŒã¯ã©ã¹ãšã¯ã©ã¹ã€ã³ã¹ã¿ã³ã¹ã®äž¡æ¹ã§äœ¿çšå¯èœïŒã¯ãæ¥ç¶ããã©ã³ã¶ã¯ã·ã§ã³ããŸãã¯ãµããã©ã³ã¶ã¯ã·ã§ã³ã§ãã 3ã€ã®ãšã³ãã£ãã£ãã¹ãŠãåãã€ã³ã¿ãŒãã§ã€ã¹ãæäŸããå éšã®ããããªéããããããã人çã®äœ¿çšã«éãã¯ãããŸããã connection.transactionïŒïŒãåŒã³åºããšãæ¥ç¶ã¯ãã©ã³ã¶ã¯ã·ã§ã³ãè¿ããŸãããã©ã³ã¶ã¯ã·ã§ã³ã¯ãµããã©ã³ã¶ã¯ã·ã§ã³ã§ããããµããã©ã³ã¶ã¯ã·ã§ã³ã¯å¥ã®ãµããã©ã³ã¶ã¯ã·ã§ã³ã§ãã
å éšã®éãã¯æ¬¡ã®ãšããã§ããæ¥ç¶ã®ã³ãããããã³ããŒã«ããã¯ã¡ãœããã¯ãšã©ãŒãã¹ããŒããŸãããã©ã³ã¶ã¯ã·ã§ã³ãæåŸ ã©ããã«åäœããå Žåããµããã©ã³ã¶ã¯ã·ã§ã³ã®å Žå-ã³ãããã¯äœããããããŒã«ããã¯ã¯èŠªãã©ã³ã¶ã¯ã·ã§ã³ãããŒã«ããã¯ããŸãã
ããã¯ãããšãã°RecordïŒsaveïŒïŒã®ããã«ãäžéšã®ã¡ãœããããã©ã³ã¶ã¯ã·ã§ã³ãçæããæåŸã«çµäºããããã«è¡ãããŸãã ãã®ãããªã¡ãœããããã©ã³ã¶ã¯ã·ã§ã³ã®ãã¬ãŒã ã¯ãŒã¯å ã§æ£ããæ©èœããããã«ããµããã©ã³ã¶ã¯ã·ã§ã³ã®ç¡éã®ãã¹ããå®è£ ãããŠããŸãã
2çªç®ã®æ¹æ³-ã·ã³ãã«-ã§ã¯ããã³ã¬ãŒã¿ã䜿çšãããŸãïŒ
import {Record, acceptsTransaction} from 'agregate' class Post extends Record { @acceptsTransaction async static create(text) { return await new this({text}).save(this.connection) } }
ã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
import {Record, acceptsTransaction} from 'agregate' class Post extends Record { async static create(text, transaction) { //Queryable - , Connection, Transaction, SubTransaction if (transaction instanceof Queryable) this.connection = transaction try { const result = await new this({text}).save(this.connection) if (transaction) await transaction.commit() return result } catch (e) { if (transaction) await transaction.rollback() throw e } } }
äžèšã®äŸã®ããã«ããã³ã¬ãŒã¿ãçŽæ¥äœ¿çšããŠæ§æã§ããŸãã æ§æã®å ŽåãçŸåšäœ¿çšå¯èœãªãã©ã°ã¯1ã€ã ãã§ã-forceã¯ããã©ã³ã¶ã¯ã·ã§ã³ã匷å¶çã«äœæããŸã-ãã©ã³ã¶ã¯ã·ã§ã³ã転éãããªãå ŽåããããäœæããŸãã 次ã®ããã«äœ¿çšããå¿ èŠããããŸãïŒ
@acceptsTransaction({force: true}) ...
泚æããŠãã ãã-çŸåšãthis.connectionã¯ãã©ã³ã¶ã¯ã·ã§ã³ã«ãªã£ãŠããŸãã é¢æ°ãå®äºãããšãããããã£ã¯ä»¥åã®ç¶æ ã«æ»ããŸããããã©ã³ã¶ã¯ã·ã§ã³ãæž¡ãããšãå¿é ããã«ã¯ã©ã¹ã®ä»ã®ã¡ãœãããåŒã³åºãããšãã§ããŸãã ãã®éæ³ã¯ããã§ã®ã¿æ©èœããŸãïŒäºæž¬å¯èœã§ãïŒã
ãã©ã³ã¶ã¯ã·ã§ã³ã¯é çªã«åŠçããããããã€ãŸã ãããªããžã§ã¯ããå®äºãããŸã§ãå¥ã®ãªããžã§ã¯ããéå§ããããŸã§ããªããžã§ã¯ãã¯è€è£œãããªãããããã®ãã³ã¬ãŒã¿ã§éçã¡ãœãããã©ãããããšã誀ã£ãŠãã©ã³ã¶ã¯ã·ã§ã³ã倱æããå¯èœæ§ãããããšã«æ³šæããŠãã ããã ã¯ã©ã¹ã€ã³ã¹ã¿ã³ã¹ã®å Žåãããã¯ãJSãæ£ããæäœããå Žåããã®ã¹ã³ãŒãå ã«ãããä»ã®å®è¡ã¹ã¬ããïŒpromiseãasyncãªã©ïŒããåæã«ã¢ã¯ã»ã¹ã§ããªããšããäºå®ã®ãããæãããšã§ã¯ãããŸããããªããžã§ã¯ãã«ã¢ã¯ã»ã¹ã§ããŸããã
ããããŠãããå šäœã§ã
APIã®èª¬æãšãAPIããã®æ¹æ³ã§å®è¡ãããçç±ãšãããã§ãªãçç±ã¯å®äºã§ãã
è¿œå ãã䟡å€ã®ããå¯äžã®ããšã¯ãèªåãå人ã®ããã®å°ããªãããžã§ã¯ãã§æ¢ã«äœ¿çšããŠããããšã§ãã é·ãéãããŒã¿ããŒã¹ãæäœãããšãããã®ãããªåã³ãæããŠããŸããã§ãã-Ruby / Railsã§äœæ¥ããŠãããšãã ããäœæ¥ã¡ã«ããºã ã®ãéææ§ããšç解å¯èœæ§ã®æèŠãæããŸããã
ãŠãããã«ã¯æ©èœãé床ãäžè¶³ããŠããå ŽåããããŸããããããå¿ èŠãªå Žåã¯ãããžã§ã¯ãã«æ¥ç¶ããŠãã ããã éèšã¯ãããªãããçµç¹åãããã³ãŒãã®ããã608è¡ïŒã·ã§ãã¯ïŒã§ãããå€æŽãè¿œå ãæŽæ°ãããã³è¿œå ã®ãã¹ããè¡ãã®ã¯éåžžã«ç°¡åã§ãã 倧èŠæš¡ãªãããã¯ã·ã§ã³ã§ã®äœ¿çšã«ç¹ã«é©ããŠããããšã確èªããããšæããŸãããããããæ°ã«å ¥ã£ãããéçºã«åå ããŠãã ããïŒ