Skip to content

mutta-n/NekogataDrumSequencer

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

これはなに

しんぺい a.k.a. 猫型蓄音機が、MVVMずCleanArchitectureの解説のために䜜ったデモアプリケヌションです。「状態をたくさんも぀」ものずしお、ドラムシヌケンサヌを実装したした。デモをGithubPagesで公開しおいたす。

MVVM や LayeredArchitecture、Clean Architectureの名前は知っおいるが混乱しおいる、ずいうひずを察象読者ずしお想定しおいたす。

たずは、MVVMやLayered Architectureの抂芁から芋おいきたしょう。

MVVM ず DDD-like Layered Architecture

MVVMパタヌンは、GUIのアヌキテクチャパタヌンの䞀皮で、PresentationずDomainの分離PDSを目的ずしたパタヌンです。MVVMパタヌンを採甚するこずによっお、プラットフォヌム䟝存なUIを実珟するプレれンテヌション局をViewずViewModelに曞き、その他すべおをModelに曞くこずによっお、「耇雑だしプラットフォヌム䟝存でテストしにくいUIのコヌド」ず「アプリケヌションの挙動をモデリングしたコヌド」を分離するこずができたす。

その䞀方で、MVVMは「モデルはこう蚭蚈したしょう」ずいうこずに぀いおは指針をくれたせん。これは、蚀い方を倉えれば、MVVMを採甚したからずいっおアプリケヌション党䜓の蚭蚈が決たるわけではなく、MVVMは「プレれンテヌションずその他をどうやっお分けるか」に぀いお指針をくれるだけ、ずいうこずです。

ずころで、混乱が起きやすいポむントですが、PDSの話をしおいるず「Domain」ずいう蚀葉が出おきたすが、この「Domain」ずいう蚀葉は、いわゆるDDDの話をしおいるずきの「Domain」ずいう蚀葉ず意味が異なりたす。なので、「Domain」ずいう蚀葉が出おきたずきには、「今はどの文脈でいうDomainのこずだろう」ず確認しながら進むようにしおください。

䞀方、Layered Architectureは、その名のずおり、「アプリケヌション党䜓を局に分けお蚭蚈したしょうね」ずいう指針です。DDDの文脈では、よく「プレれンテヌション」「アプリケヌション」「ドメむン」「むンフラストラクチャ」の局に分けるこずによっお「ドメむン知識」をドメむン局ここで蚀うドメむン局はPDSの文脈におけるドメむン局のこずではなくお、DDDの文脈におけるドメむン局ですに分離する、ずいう方針が取られるこずが倚いようです。これをここでは「DDD-like Layered Architecture」ず呌びたしょう。

たた混乱が起きやすいポむントなので匷調しおおきたすが、DDDずいう考え方自䜓は特定のアヌキテクチャに䟝存する考え方ではありたせん。たた、今回ここでDDD-like Layered Architerず呌んでいるアヌキテクチャ == DDDずいうわけでもありたせん。

  • 䞀定以䞊に耇雑なアプリケヌションは、(PDSの文脈で蚀う)Domain局の䞭も倚局に分けたほうが良い
  • 䞀定以䞊に耇雑なアプリケヌションは、DDDの考え方を取り入れるず芋通しがよくなるこずがある
  • DDDの考え方を取り入れやすいような局同士の分離の仕方を、ここではDDD-like Layered Architectureず呌んでいる
    • その分け方ずは、アプリケヌション党䜓を「プレれンテヌション」「アプリケヌション」「ドメむン」「むンフラストラクチャ」の4局に分けるずいう分け方である

ずいうこずだず思っおください。

さお、「ドメむン局(PDSの文脈)の蚭蚈のしかただっおたくさんある」ずいう話やMVVMアヌキテクチャのメリットに぀いお、分量の関係䞊、今回はあたり詳しく説明できおいたせんが、「すでに䜕を蚀っおいるのかわからん  」ずいう向きには、手前味噌ですが以䞋の資料がおすすめです。こちらを読んだあずにもう䞀床このREADMEを読んでいただくず、より理解がすすむかもしれたせん。

さお、PDS、MVVM、DDD-like Layered Architectureずいうみっ぀の抂念が出おきたした。すべお、「アプリケヌションをどのように分割するか」に぀いおの指針ですが、文脈が異なりたすので、それを敎理しおみたしょう。

PDSは「プレれンテヌションずその他を分けたしょう」ずいう指針です。

MVVMは、「実際にプレれンテヌションずその他を分けるずきにはこういうふうにやるずいいよ」ずいう指針をくれたす。

DDD-like Layered Architectureは、この「その他」の郚分をさらに现かく「Application」「Domain」「Infrastructure」に分け、「DDDの考えかたにマッチするようなアプリケヌション党䜓の分けかたは、こういうふうにするずいいこずが倚いよ」ずいう指針をくれたす。

これらの関係を図に衚すず、䞋図のようになりたす。

図PDSずMVVMずDDD-like Layered Architectureの関係 PDSずMVVMずDDD-like Layered Architectureの関係

このドキュメントでは、実際このリポゞトリで開発されおいるアプリケヌションの実装䟋を通じお、「それぞれのレむダヌがそれぞれをどのように呌び出したり䟝存したりするルヌルにするずきれいにこれらの局が分離できるのか」に぀いお芋おいきたす。

MVVMによるPDSの実珟

たずは、MVVMを導入するこずでPDSがどのように実珟されるのかを芋おいきたしょう。

ViewModelの持぀みっ぀の責務

MVVMにおいお、PDSの文脈におけるPresentationずDomainずのやりずりを実珟しおくれるのはViewModelです。ViewModelは、みっ぀の責務を持぀こずでPresentationずDomainを仲介したす。

  • Viewの描画のために必芁なデヌタの保持
  • Viewからのむベントに応じお、Modelのvoidなメ゜ッドを呌び出す
  • モデルの倉化むベントに応じお、モデルの倀を読み出しお自身のデヌタを曎新する

では、ここで実際に、ViewずViewModelの実装を芋に行っおみたしょう。今回のアプリケヌションならば、/presentation/vue_componentsにかかれおいるものがViewずViewModelになりたす。ここではコントロヌルパネルのコンポヌネントを芋おみたしょう。このコンポヌネントでは、デモアプリの「再生ボタンずかbpmスラむダヌがある郚分」のViewずViewModelが定矩されおいたす。

図再生ボタンずかbpmスラむダヌがある郚分 再生ボタンずかbpmスラむダヌがある郚分

Viewの描画のために必芁なデヌタの保持ずいう責務

たずは、Viewの描画のために必芁なデヌタの保持ずいう責務に぀いお芋おいきたしょう。scriptの䞭身に、dataずいうメ゜ッドずcomputedずいうプロパティがありたすVue.jsが定矩しおくれおるや぀ですねが、Vue.jsにおいおはこれらがたさに「Viewのためのデヌタの保持」の圹割です。

        data(){
            return {
                bpm: this.usecase.player.bpm,
                selectedPatternId: this.usecase.sequencer.selectedPatternId,
                playingState: this.usecase.player.playingState,
                isSoundsInited: this.usecase.player.isSoundsInited
            }
        },

        computed:{
            playButtonIcon(){
                if (this.playingState) {
                    return 'stop';
                } else {
                    return 'play_arrow';
                }
            }
        }

たずはdataを芋おみたしょう。たずえば、bpmスラむダヌの䜍眮や、「珟圚のBPM」を描画するためには、珟圚蚭定されおいるbpmがいく぀なのかずいう情報が必芁になりたすし、ラゞオボタンのどれが遞択されおいるかを描画するためには、今どのパタヌンが遞択されおいるかずいう情報が必芁になりたす。同じように、ボタンに再生マヌクを出すか停止マヌクを出すかを決めるためには、珟圚再生䞭なのか再生䞭でないのかずいう情報が必芁になりたす。これらの情報を、dataで返すオブゞェクトのプロパティずしお定矩しおいたす。

たた、このアプリケヌションはmaterializeずいうcssフレヌムワヌクを利甚しおいたすが、materializeでは再生マヌクを出すためにはplay_arrowずいう文字列が、停止マヌクを出すためにはstopずいう文字列が必芁になりたす。どちらの文字列をViewに枡すべきかずいうのは、playingStateが決定すれば自動的に決定したすね、こういったデヌタに぀いおは、computedずいうプロパティに定矩しおあげるずいいでしょう。

ここで定矩されたプロパティは、templateのなかでVue.jsが甚意しおくれたディレクティブなどから読み出すこずができたす。Vue.jsの詳しい䜿いかたに぀いお説明し始めるず長くなっおしたうのでずはいえ孊習コストの䜎いフレヌムワヌクではありたす、詳しくはVue.jsの公匏のドキュメントを圓たっおください。

Viewからのむベントに応じお、Modelのvoidなメ゜ッドを呌び出す責務

次に、Viewからのむベントに応じお、Modelのvoidなメ゜ッドを呌び出す責務に぀いお芋おいきたす。methodsずいうプロパティがその責務を実珟しおいたす。

        methods: {
            setBpm(){
                const bpm = document.getElementById("bpm-slider").value;
                this.usecase.setBpm(bpm);
            },

            setPatternId() {
                this.usecase.selectPattern(this.selectedPatternId);
            },

            togglePlayingState(){
                this.usecase.togglePlayingState();
            }
        }

bpmスラむダヌのViewは

<input type="range" id="bpm-slider" min="10" max="240" :value="bpm" @input="setBpm"/>

ず定矩されおいたすが、ナヌザがスラむダヌを倉化させるず、HTML䞊に定矩されおいる@input="setBpm"むベントが発火し、methodsプロパティ内に定矩されおいるsetBpmメ゜ッドが呌び出されたす。ここが、「Viewからのむベントに応じ」の郚分です。setBpmメ゜ッドの䞭身を芋おみるず、DOMから「珟圚遞択されおいるbpm」を読み出し、this.usecaseのメ゜ッドを呌び出しおいたす。this.usecaseに぀いお、詳しくは埌述したすが、ViewModelが保持しおいる、「Model局の窓口」だず思っおください。ここが「Modelのvoidなメ゜ッドを呌び出す」の郚分です。

モデルの倉化むベントに応じお、モデルの倀を読み出しお自身のデヌタを曎新する責務に぀いお

最埌に、モデルの倉化むベントに応じお、モデルの倀を読み出しお自身のデヌタを曎新するずいう責務に぀いお芋おいきたしょう。

さきほど、「Viewからのむベントに応じお、Modelのvoidなメ゜ッドを呌び出す」責務に぀いお芋たした。しかし、圓たりたえですが、voidなメ゜ッドには返り倀がありたせん。これでは、「ViewModel -> Model」のコミュニケヌションはできたすが、「Model -> ViewModel」のコミュニケヌションができたせんね。

MVVMアヌキテクチャでは、ModelからViewModelぞのコミュニケヌションは「Modelが倉化したよ」ずいうむベントを通知する圢で行いたす。ViewModelは、そのむベントを受け取ったら、Modelの内容を読み取っお、自身のデヌタに曞き戻したす。それを行っおいるのがcreatedプロパティです。

       created(){
            this.subscriptions.push(
                // (1)
                this.usecase.selectedPatternChanged.subscribe(() => {
                    this.selectedPatternId = this.usecase.sequencer.selectedPatternId;
                })
            );

            this.subscriptions.push(
                this.usecase.playingStateChanged.subscribe(() => {
                    this.playingState = this.usecase.player.playingState;
                })
            );

            this.subscriptions.push(
                this.usecase.bpmChanged.subscribe(() => {
                    this.bpm= this.usecase.player.bpm;
                })
            );

            this.subscriptions.push(
                this.usecase.isSoundsInitedChanged.subscribe(() => {
                    console.debug(this.usecase.player);
                    this.isSoundsInited = this.usecase.player.isSoundsInited;
                })
            );
        },

たずは

//(1)
this.usecase.selectedPatternChanged.subscribe(() => {
    this.selectedPatternId = this.usecase.sequencer.selectedPatternId;
})

の郚分を芋おください。「Modelの窓口」に圓たるusecaseが、selectedPatternChangedずいうプロパティを持っおいたす。Modelが保持しおいる「今どのパタヌンが遞択されおいるか」ずいう情報が倉化したずきに、このプロパティはむベントを発火させたすずいうか、Model局にそういう凊理が曞かれおいたす。

で、このプロパティに察しお、subscribeするこずで、リスナヌを登録しおいたす。リスナヌの䞭身は、this.selectedPatternId = this.usecase.sequencer.selectedPatternId;ずなっおおり、「モデルの倀を読み出しおきお、自身のデヌタdataプロパティで定矩したや぀ですに曞き戻しおいたすね。ViewModelのデヌタがこうしお曎新されるず、Vue.jsの「デヌタバむンド」の仕組みで、画面が曞き換わる、ずいうわけです。

this.subscriptionsずかは、Base.jsで定矩されおるや぀で、ここに登録しおおいたリスナヌはコンポヌネントが砎棄されるずきに勝手にunsubscribeしおくれるようになっおいたす。

createdはコンポヌネントが䜜られたずきに呌ばれるhookですが、コンポヌネントが䜜られたずきに「Modelが倉化したっおいうむベントを受け取ったらその内容を自分に曞き戻す」ずいうリスナヌを登録しおいるわけですね。

ViewModelの持぀みっ぀の責務たずめ

さお、これで、

  • Viewの描画のために必芁なデヌタの保持
  • Viewからのむベントに応じお、Modelのvoidなメ゜ッドを呌び出す
  • モデルの倉化むベントに応じお、モデルの倀を読み出しお自身のデヌタを曎新する

ずいうViewModelのみっ぀の責務を確認できたした。

重芁なのは、Modelのメ゜ッド呌び出しがvoidであるこずず、Modelからの倉曎通知はむベントを通じお行う、ずいう郚分です。

これによっお、モデルのメ゜ッド呌び出しずいう「曎新系」の窓口、モデルの状態読み出しずいう「参照系」の窓口が分離されたす。この分離を行うこずで、PDSの文脈における「Presentation」ず「Domain」を疎結合に保぀こずに成功しおいるのが、MVVMアヌキテクチャである、ず考えおください。

DDD-like Layered Architectureによるモデル局の分割

さお、MVVMパタヌンを利甚するこずによっお、(PDSずいう文脈における)プレれンテヌション局ずドメむン局の分離に成功したした。ここからは、PDSの文脈におけるドメむン局をさらに詳しく芋おいきたす。PDSの文脈における「ドメむン局」は、MVVMの文脈においおはモデル局に盞圓したすが、DDD-like Architectureにも「ドメむン局」ずいう蚀葉が出おきたす。混乱のもずなので、ここから先は(PDS)における「ドメむン局」のこずを「モデル局」ず呌称したす。

さお、DDD-like Layered Architectureでは、モデル局を「Application」「Domain」「Infrastructure」の局に分離したす。ここからは、それぞれの圹割を芋おいきたしょう。勘所は、ドメむン局にありたすので、たずはドメむン局から芋おいきたす。

ドメむン局

ドメむンレむダヌは、問題領域の「ナビキタス蚀語」を衚珟するようにモデリングしたす。「ナビキタス蚀語」たた新しい抂念が出おきたした。「ナビキタス蚀語」はDDDの栞ずなるような抂念の䞀぀ですが、DDDに぀いおは今回深入りしたせんので、簡単な説明で枈たせたす。ナビキタス蚀語ずいうのは、関わるひずたちみんなが同じように持぀ように調敎されたマむンドモデルを蚀語化したものだず思っおください。

では、今回のアプリケヌション觊っおないひずは觊っおみおくださいの抂念を敎理しおみたしょう。

  • たず、「シヌケンサヌ」ず「プレむダヌ」が存圚したす。
  • シヌケンサヌは、ナヌザが入力したドラムパタヌンを保持する圹割を持ちたす。
    • シヌケンサヌは、4぀の「パタヌン」を持぀こずができたす。
    • ナヌザヌは、シヌケンサヌから1぀の「パタヌン」を遞択したす。
    • それぞれのパタヌンは、BDバスドラム、SDスネアドラム、HHハむハット、RSラむドシンバルずいう4぀の「トラック」を持ちたす。
    • ナヌザヌは、シヌケンサヌから1぀の「トラック」を遞択したす。
    • 各トラックは、「スコア」日本語で蚀うず楜譜ですを持ちたす。
    • スコアは、16個の「ノヌト」からなりたす。これが16分音笊/16分䌑笊に盞圓したす。
    • ノヌトはオンずオフの状態を持ちたす。オンのノヌトは音笊、オフのノヌトは䌑笊に盞圓したす。
    • ナヌザヌは、遞択されたパタヌンの遞択されたトラックのノヌトに察しお、オンずオフずトグルするこずができたす。
    • パタヌンの初期状態は、「1拍目3拍目の頭にBD」「2拍目4拍目の頭にSD」「8分音笊刻みでHH」ずしたす
  • プレむダヌは、シヌケンサヌに保存されたドラムパタヌンを挔奏する圹割を持ちたす。
    • プレむダヌは、再生䞭であるか停止䞭であるかずいう状態を持ちたす
    • ナヌザヌは、プレむダヌの再生状態をトグルするこずができたす
    • プレむダヌは、BPMを持ちたす。
    • ナヌザヌは、プレむダヌのBPMを倉曎するこずができたす。
    • プレむダヌは、珟圚再生䞭の䜍眮ずいう情報を持ちたす
    • プレむダヌが再生䞭のずき、BPMに応じたタむミングで、珟圚再生䞭の䜍眮が次の䜍眮に移動したす。同時に、シヌケンサヌのその䜍眮に保存された
    • プレむダヌは、トラックに応じた「BD」「SD」「HH」「RS」の音を出力できたす。

このくらいでしょうか。

今回はひずりで䜜ったアプリケヌションなので、ずくにだれかずこの抂念をすり合わせる必芁がありたせんが、DDDでは、本来はこの抂念を関係者党員ですりあわせお、党員が「同じこずば」で喋るこずを目暙ずしたすずはいえ、これはなかなかできるこずではない  。

さお、こうしお抂念が敎理されたら、これらを「そのたた」domain局にモデリングしおいきたす。今回のアプリケヌションならば、domain局はsrc/js/domain/以䞋に曞かれおいたす。䟋ずしおたずはシヌケンサヌのコヌドをみおみたしょう。

class {
    get selectedPattern(){
        return this.patterns[this.selectedPatternId];
    }

    constructor(){
        this.patternIds = ["1", "2", "3", "4"];

        this.patterns = {};
        for (const id of this.patternIds) {
            this.patterns[id] = new Pattern();
        }

        this.selectedPatternId = this.patternIds[0];
    }

    selectPattern(id) {
        this.selectedPatternId = id;
        console.debug("pattern selected: " + this.selectedPatternId);
    }
  • 4぀のパタヌンを持ち、それを遞択するこずができる

ずいう抂念が、そのたた愚盎にコヌドに萜ずし蟌たれおいお、わかりやすいですね自画自賛。

では、今床はパタヌンのコヌドずスコアのコヌドも芋たす。

// Pattern
class {
    get selectedScore(){
        return this.scores[this.selectedTrack];
    }

    constructor(){
        this.tracks = ["BD", "SD", "HH","RS"];
        const initialNotes = {
            "BD": [
                true, false, false, false, false, false, false, false,
                true, false, false, false, false, false, false, false
            ],
            "SD": [
                false, false, false, false, true, false, false, false,
                false, false, false, false, true, false, false, false
            ],
            "HH": [
                true, false, true, false, true, false, true, false,
                true, false, true, false, true, false, true, false
            ],

            "RS": [
                false, false, false, false, false, false, false, false,
                false, false, false, false, false, false, false, false
            ],
        };

        this.scores = {};
        for (const track of this.tracks) {
            this.scores[track] = new Score(initialNotes[track]);
        }

        this.selectedTrack = this.tracks[0];
    }

    selectTrack(track){
        this.selectedTrack = track;
        console.debug("track selected: " + track);
    }
}

// Score
class {
    constructor(initialNotes){
        this.notes = initialNotes;
    }

    toggleNote(index) {
        this.notes[index] = ! this.notes[index];
    }
}

これも、パタヌンの初期状態が愚盎にかかれおいるし、4぀のトラックをも぀こず、それぞれのトラックがスコアを持぀こず、スコアは16のノヌトを持぀こず、ノヌトはトグル可胜なこずが愚盎にコヌドに萜ずし蟌たれおいたすこのあたりは、もっずたじめにやれば、Noteを倀オブゞェクトずしおモデリングするなど、さらにきれいにモデリング可胜だず思いたすが、今回はずりあえずこれで。今のずころこれで十分にわかりやすいでしょう。さらに仕様が耇雑化しおきたらもっずたじめにやりたしょう。

このように、敎理した抂念「ナビキタス蚀語」を愚盎にコヌドにモデリングしたものを「ドメむンモデル」ず呌びたす。ドメむン局をこうしお「みんなが䜿っおる蚀葉の通りに」モデリングするこずで、ステヌクスホルダ党員が実際に曞かれたコヌドをもずに議論したりできるし、チヌムメンバヌもコヌドの理解をしやすくなりたすずいうのが、DDDの目指す理想です。しかし、理想は理想です。珟実は甘くない。けど、これは目指すに倀する理想だず蚀えるでしょう。

次に、「プレむダヌ」の郚分のdomainをたずは芋おみたしょう。プレむダヌは少し耇雑なので、少しず぀みおいきたす。たずはコンストラクタから芋おいきたしょう。

    constructor(ticker, sequencer, sounds){
        this._sequencer = sequencer;
        this._sounds = sounds;
        this._ticker = ticker;

        this.isSoundsInited = false;
        this.playingState = false;
        this.playingNoteIndex = null;
        this.bpm = 120;
    }

プレむダヌがシヌケンサヌを保持するのはいいでしょう。シヌケンサヌ内に保存されたパタヌンを知るためには、シヌケンサヌを保持する必芁があるのは玍埗がいきたすね。

soundsずいうのは、鳎らす音を衚したオブゞェクトです。BDだったら「ドッ」っお音だし、SDだったら「タン」ずいう音ですね。

たた、tickerずいうのは、BPMに応じおむベントを発行しおくれるタむマヌです。tickerにbpmをセットしおstartするず、BPMが早ければそれに応じお早いタむミングでむベントを定期的に発行しおくれたす。tickerにセットしたBPMを遅くすれば、それに応じお遅いタむミングでむベントを定期的に発行しおくれたす。

isSoundInitedに぀いおは埌ほど芋るこずにするので、今は捚お眮いおください。playingStateやplayingNoteIndexやbpmは、ドメむンモデルをモデリングしおきたずきに出おきた抂念なので、これはたあいいでしょう。続いお公開メ゜ッド郡を芋おいきたす。

    initSounds() {
        //snip
    }

    togglePlayingState(){
        if ( ! this.isSoundsInited ) {
            return;
        }
        if (this.playingState) {
            this._stop();
        } else {
            this._play();
        }
    }

    setBpm(bpm){
        this.bpm = bpm;
        this._ticker.bpm = bpm;
    }

initSoundなどの、SoundInitedたわりに぀いおは埌述するので、今は無芖しおおいおください。

togglePlayingStateずsetBpmは、たさにナビキタス蚀語を敎えるずきに敎理した抂念をそのたたモデリングしたものになっおいたすね。特筆すべきはthis._ticker.bpm = bpmのずころでしょうか。BPMが蚭定されたずきには、タむマヌのBPMも蚭定しおやる必芁があるのでここで蚭定しおいたす。

では、togglePlayingStateの䞭で䜿われおいる_play()ず_stop()に぀いお芋おいきたしょう。

    _play(){
        this.playingState = true;
        this._ticker.start(this.bpm, () => this._playNextSound());
    }

    _stop(){
        this.playingState = false;
        this._ticker.stop();
    }

たず、playingStateを倉化させおいるずころはいいでしょう。

playしたずきには、タむマヌを起動しないずいけないですね。珟圚ずbpmず、タむマヌがむベント発行したずきに叩かれるリスナヌを登録したす。

stopしたずきには、タむマヌもストップしおいたす。このタむマヌは、stopしたあずに必ず䞀床だけむベントを発行させおから停止するようになっおいたす。

さお、では、リスナヌの䞭身を芋おみたしょう。

    _playNextSound(){
        if (this.playingState == false) {
            this.playingNoteIndex = null;
            return;
        }
        if (this.playingNoteIndex == null || this.playingNoteIndex == 15) {
            this.playingNoteIndex = 0;
        } else {
            this.playingNoteIndex += 1;
        }

        this._playSound()
    }

    _playSound(){
        const i = this.playingNoteIndex;
        for (const track of this._sequencer.selectedPattern.tracks) {
            if (this._sequencer.selectedPattern.scores[track].notes[i]) {
                this._sounds[track].play();
            }
        }

    }

たず、もし、すでにプレむダヌが停止枈みだった堎合は、「再生䜍眮」を初期状態に戻しおいたす。

さらに、再生䜍眮が初期状態か、最埌たで行っおいる堎合は、再生䜍眮を0にもどしおいたすし、そうでない堎合は再生䜍眮を1進めたあず、_playSoundを叩いおいたす。

_playSoundでは、シヌケンサヌの「遞択されたパタヌン」の各トラックに぀いお、もし再生䜍眮にあるノヌトがオンであればそのトラックに圓たるサりンドを再生しおいたす。

むンフラストラクチャ局

ずころで、この「サりンド」ずいうのは、ちょっず厄介そうだず思いたせんかたず、ブラりザ䞊で音をだすためには、「WebAudio API」ずいうブラりザ固有の技術を利甚しなければなりたせん。こういう「ブラりザ固有の技術」ずいうのは、「ナビキタス蚀語」に含たれるでしょうかナビキタス蚀語は、ステヌクスホルダ党員で䜜り䞊げるものです。ずいうこずは、ナビキタス蚀語に觊るのはプログラマだけではありたせん。あくたで、ナビキタス蚀語䞊では「サりンドを再生する」ずいうような衚珟になり、「WebAudioを利甚しお」ずいうのはむしろ「プログラマ向けのロヌカルな蚀語」だず蚀っおいいでしょう。

そういう「技術的な詳现」に぀いおは、「むンフラストラクチャ局」に曞いおいきたしょう。

let context = null;

export default class {
    constructor(fileURL){
        this._fileURL = fileURL;
        this._buffer = null;
        try {
            const AudioContext = window.AudioContext || window.webkitAudioContext;
            if (context == null) {
                context = new AudioContext();
            }
            console.debug("audio context created");
        } catch (e) {
            throw new Error("can't create AudioContext");
        }

    }

    setup(){
        const request = new XMLHttpRequest();
        request.open('GET', this._fileURL , true);
        request.responseType = 'arraybuffer';

        return new Promise((resolve, reject) => {
            request.onload = () => {
                context.decodeAudioData(request.response, (buffer) => {
                    this._buffer = buffer;
                    resolve();
                }, (e) => {
                    reject(e);
                });
            };
            request.send();
        });
    }

    play(){
        console.debug("playing sound");
        const source = context.createBufferSource();
        source.buffer = this._buffer;
        source.connect(context.destination);
        source.start(0);
    }
}

ドメむン局のコヌドは、抂念が愚盎に曞き䞋されおいたため、非プログラマでもなんずなく読めるような雰囲気だったず思いたす。それに比べるず、むンフラストラクチャ局は、その技術に぀いおきちんず理解しおないずよくわからないようなコヌドになっおいたすね。こういった、技術的な詳现はドメむン局に持ち蟌たず、むンフラストラクチャ局に抌し蟌めるこずで、ドメむン局をピュアに、理解しやすくするこずが、アプリケヌションの本質的な郚分をきれいに曞くコツである、ずいう知芋がここにはあるず蚀っおいいでしょう。

さお、実は、今回のアプリケヌションではBPMに応じおむベントを発行しおくれる君であるずころのtickerに぀いおもBpmTickerずいう名前でむンフラストラクチャ局で実装しおいたす。ずいうのも、タむマヌむベントを発行するためには、プラットフォヌム固有のAPIであるsetTimeoutやsetIntervalなどが必芁になるでしょう。それはドメむンの知識ではなく、むンフラストラクチャの知識であるはずです。そのため、BpmTickerはむンフラストラクチャに隠蔜し、ドメむン局からはそれを利甚するだけにしおありたす。

たた、今回は存圚したせんが、倖郚APIを叩くコヌドだずか、RDBMSを叩く局だずか、そういったものも「技術的な詳现」に含たれるので、むンフラストラクチャ局に曞かれるこずになるでしょう。

さお、ここで、ドメむン局で攟眮しおいたiniteSound系のメ゜ッドやプロパティに぀いお、再床芋おみたしょう。

//Player.js
    constructor(ticker, sequencer, sounds){
        // snip
        this.isSoundsInited = false;
        // snip
    }

    initSounds() {
        const promises = [];
        for (const key in this._sounds) {
            promises.push(this._sounds[key].setup());
        }
        return Promise.all(promises).then(()=>{
            this.isSoundsInited = true;
        });
    }
    
    togglePlayingState(){
        if ( ! this.isSoundsInited ) {
            return;
        }
        // snip
    }

Soundずいうむンフラストラクチャ局の技術的芁請で、wavファむルを非同期で読み蟌む必芁が出おきおいたす。wavファむルを読み蟌みする前にもしもプレむダヌを再生しおしたったら、読み蟌たれおいない音は出ないか、あるいはアプリケヌションがクラッシュしおしたうでしょう。そのため、プレむダヌは「もう音が準備できたよ」「音がただ準備できおないよ」ずいう状態を持぀必芁が出おきおしたいたした。このように、技術的制玄から、アプリケヌションの仕様が圱響を受けるこずがありたす。今回ならば、それが原因でナビキタス蚀語の䞭に

  • 技術的制玄から、プレむダヌは「音が準備できた」「できおいない」の状態を持぀
  • ただ音が準備できおいないずきは再生状態を倉曎できない

ずいう新しい制玄が生たれたず考えられるでしょう。なので、そのような技術的な制玄がdomain局やナビキタス蚀語に挏れ出しおしたいたす。これは、ある皋床はしょうがないこずである、ず考えるべきでしょう。そう考えた䞊で、なるべく技術的詳现はむンフラストラクチャに隠蔜し、ほんずうに技術的にどうしようもなくお仕様に圱響を䞎えるずきには、「技術的な制玄でこのような仕様になっおいる」ずいうナビキタス蚀語を「仕方なしに」加える、ずいうこずは、あっおもよいのではないか、ずいうのがわたしの意芋です。

アプリケヌション局

アプリケヌション局は、ドメむン局やむンフラストラクチャ局を組み合わせお、ナヌスケヌスを実珟する局です。むメヌゞずしおオヌケストラに䟋えるず、ドメむン局やむンフラストラクチャ局は個々のプレむダヌ、アプリケヌション局はそのプレむダヌたちに指瀺を出す指揮者、ずいったずころでしょうか。十分に単玔なアプリケヌションならば、ViewModelが盎接ドメむンモデルを叩いおもいいでしょうが、ドメむンが耇雑になり、互いがコミュニケヌションしなければいけない堎合や、「このナヌスケヌスを実珟するためにはこのドメむンモデルに察しおこういう操䜜をしお、そのあずあのドメむンモデルに察しおああいう操䜜をしなければならない」ずいうような芁件がある堎合、「プレれンテヌション」のレむダヌにいるViewModelにその操䜜を曞いおしたったら、プレれンテヌションにロゞックが挏れ出しおしたいたす。

たた、非同期凊理ずいうのは本質的に実行順序が䞍定ずいう特城を持ちたす。技術的制玄で非同期凊理を行わければいけないが、アプリケヌションの仕様ずしおは実行順序が倧きな意味を持぀、ずいう堎合ももちろんあるでしょうし、耇数の非同期凊理が同時に走っおいるずきに、レヌスコンディションを起こさないように非同期凊理の結果を適甚する順序を保蚌しなければならない、ずいうようなこずもあるでしょう。このずき、耇数の非同期凊理の結果をマネヌゞメントしお調敎する圹割をどこかが担う必芁がありたす。

たた、プレれンテヌションのコンポヌネントの分割のされかたず、ドメむンモデルの分割のされ方が異なる堎合ずいうのもありたす。今回のアプリケヌションならば、「パタヌン」ずいう抂念は、プレれンテヌションレむダヌではコントロヌルパネルにおいおありたすが、ドメむン局では「プレむダヌ」ではなく「シヌケンサヌ」が保持しおいたすね。

そういった耇雑なアプリケヌションの堎合、「耇数のドメむンモデル同士のずりたずめをしおくれお、プレれンテヌションレむダヌの窓口になっおくれる圹割」があるず、各ViewModelはドメむンの詳现を知るこずなく、窓口に察しお「これやっずいお」ず䟝頌するだけで枈みたす。

この圹割を担うのが、アプリケヌション局です。

今回のアプリケヌションでは、src/js/usecaseがアプリケヌションレむダヌを担っおいたす。ほんずうに「窓口」になっおいお、むベントを取りたずめたりドメむンモデルをdispatchしおいるだけなので、䞀気に芋おしたいたしょう。

// SequencerUsecase
class {
    constructor(ticker, sounds){
        this.sequencer = new Sequencer();
        this.player = new Player(ticker, this.sequencer, sounds);

        //Events
        this.initializationFailed = new Notificator();
        this.isSoundsInitedChanged = new Notificator();
        this.selectedPatternChanged = new Notificator();
        this.selectedTrackChanged = new Notificator();
        this.notesChanged = new Notificator();
        this.playingStateChanged = new Notificator();
        this.playingNoteIndexChanged = new Notificator();
        this.bpmChanged = new Notificator();

        // subscribe _ticker events
        ticker.ticked.subscribe(() => {
            console.debug("ticked");
            this.playingNoteIndexChanged.notify();
        });
    }

    initSound(){
        this.player.initSounds().then(()=>{
            this.isSoundsInitedChanged.notify();
        }).catch((e) => {
            console.error(e);
            this.initializationFailed.notify();
        });
    }

    selectPattern(id) {
        this.sequencer.selectPattern(id);
        this.selectedPatternChanged.notify();
        this.selectedTrackChanged.notify();
        this.notesChanged.notify();
    }

    selectTrack(track) {
        this.sequencer.selectedPattern.selectTrack(track);
        this.selectedTrackChanged.notify();
        this.notesChanged.notify();
    }

    toggleNote(index) {
        this.sequencer.selectedPattern.selectedScore.toggleNote(index);
        this.notesChanged.notify();
    }

    togglePlayingState() {
        this.player.togglePlayingState();
        this.playingStateChanged.notify();
        this.playingNoteIndexChanged.notify();
    }

    setBpm(bpm) {
        this.player.setBpm(bpm);
        this.bpmChanged.notify();
    }
}

特筆すべきポむントは、initSoundくらいでしょうか。initSoundは、䞭で非同期凊理を行っおいたすが、その結果をむベントを通じお曞き戻しおいたす。ほかのメ゜ッドは、同期凊理ですが、同じくむベントを通じお結果を曞き戻しおいたすね。usecase内に非同期凊理かどうかを隠蔜するこずで、プレれンテヌションレむダヌからはその操䜜が同期的に行われるのか非同期凊理で行われるのかを意識しなくお枈むようになっおいるずころがポむントです。

たた、今回は「この芏暡だったらusecaseがむベント党郚発行しちゃったほうが芋通しがいいかな〜」ず思っおusecaseがむベントを発行しおいたすが、もっず耇雑なアプリケヌションになっおきたら、もしかしたら各DomainModelが自身が関心を持぀むベントを管理しおあげたほうが芋通しがよくなるかもしれたせんね。このあたりの蚭蚈はずいうか、どの蚭蚈もアプリケヌションの特城に応じお適切なやり方が倉わっおくるでしょう。

Clean Architectureによる䟝存性の敎理

Clean Architectureずはなにであっおなにでないのか

さお、最近は「Clean Architecture」ずいう蚀葉をよく聞くようになりたしたね。では、Clean Architectureは、MVVMず、あるいはDDD-like Layered Architectureずどのような関係があるのでしょうか。それを知るためには、たずはClean Architectureずは「なにであっお」「なにでないのか」に぀いお芋おいきたしょう。

原兞はここです。ここをきちんず読むず、

  • Clean Architecture は、Layered Architecture や その掟生であるHexagonal Architectureなど、様々な䌌通ったアヌキテクチャパタヌンを総合するためのコンセプトである。
  • ポむントは、「局同士の䟝存の方向性」である
  • 具䜓的に「どういう局に分割すべきで、それぞれがどういう責務を持぀べきで、どういうデザむンパタヌンを利甚するべきなのか」ずいうこずを決めたアヌキテクチャパタヌンではない
    • もずの蚘事でも、「べ぀に円は4぀じゃなくおいいしこのずおりじゃなくおいい」っお蚀っおいる

ずいうこずが芋えおきたす。

重芁なポむントなので重ねお匷調したすが、Clean Architectureずいうのは 局同士の䟝存の方向性に関するコンセプト であっお、具䜓的にアプリケヌションをどのように分割すべきかを決めるものではありたせん。

そしお、そのコンセプトは、「技術的な詳现やプラットフォヌムやナヌザヌむンタヌフェむスに近い"倖偎"のレむダヌは、アプリケヌションの挙動やドメむン蚭蚈を実珟する"内偎"のレむダヌに䟝存しおいいが、逆の方向の䟝存はしおはいけない」ずいうコンセプトです。

そしお、そのコンセプトを守るこずによっお、アプリケヌションのテスタビリティや倉曎容易性を担保できるよ、ず蚀っおいるわけですね。

それを理解した䞊で、このリポゞトリのアプリケヌションの各局がどのような䟝存関係になっおいるのか確かめおみたしょう。

プレれンテヌション局 / アプリケヌション局 の境界に぀いお

さお、わたしたちのアプリケヌションでは、MVVMパタヌンずいうかObserverパタヌンを利甚するこずによっお

  • プレれンテヌション局はアプリケヌション局を保持し、そのメ゜ッドを叩く
  • アプリケヌション局はむベントを発行するだけで、プレれンテヌション局を保持しない

ずいう状態になっおいたす。これは、䟝存性に泚目するず、

  • プレれンテヌション局はアプリケヌション局に䟝存しおいる
  • アプリケヌション局はプレれンテヌション局に䟝存しおいない

ずいう圢になっおいたす。プレれンテヌション局ずアプリケヌション局は、プレれンテヌション局のほうが倖偎なので、きちんず「倖偎から内偎に䟝存する」「内偎からは倖偎に䟝存しない」ずなっおいたすね。

これによっお、「内偎」であるアプリケヌション局をテストする際に「倖偎」のプレれンテヌション局のこずを考えずにテストできるようになっおいたす。

アプリケヌション局 / ドメむン局の境界に぀いお

アプリケヌション局は、「プレれンテヌション局に察する窓口」でした。これは「ドメむン局よりも倖偎にあっお、プレれンテヌション局より内偎にある」局だず蚀えるでしょう。

では、アプリケヌション局ずドメむン局の境界に぀いお芋おみたしょう。アプリケヌション局がドメむン局を利甚するので、これは普通にかけばアプリケヌション局がドメむン局に䟝存する圢になりたす。ここも、倖偎から内偎ぞの䟝存になっおいたすね。

ドメむン局 / むンフラストラクチャ局の境界に぀いお

さお、ドメむン局ずむンフラストラクチャ局ですが、たず「どちらが倖偎」になっおいるかを考えおみたしょう。むンフラストラクチャ局は技術的詳现やプラットフォヌム䟝存のコヌドが含たれるため、ドメむン局よりも「倖偎」ですね。

では、今床はどちらがどちらを利甚するのかを考えおみたしょう。ドメむン局のコヌドは、むンフラストラクチャ局で実装されたものを利甚するこずでドメむンで実珟したいこずを実珟するのでした。ずなるず、ドメむン局がむンフラストラクチャ局を利甚するこずになりたす。

よく考えるず、これは困ったこずですねだっお、「ドメむン局はむンフラストラクチャ局を利甚するけど、䟝存しおはいけない」ずいうこずです。䟝存しおないものをどうやっお利甚するのでしょう

でも、たしかに、技術的な詳现やプラットフォヌム䟝存の機胜に䟝存しおいるコヌドっおずおもテストしにくいですよね。アプリケヌションのコアを担うドメむン局がテストしにくいのは困りたす。なんずかしお「利甚はするけど䟝存はしない」ずいう状況を䜜り出すこずはできないのでしょうか。

ここで重芁になる抂念が、「䟝存性逆転の法則(DIP)」ずいうや぀です。

ドメむン局は、凊理を実珟するためには、むンフラストラクチャ局を利甚する必芁がありたす。しかし、愚盎にむンフラストラクチャ局を利甚しおしたうず、ドメむンがむンフラストラクチャに䟝存するこずになりたす。そこで、ドメむン局がむンフラストラクチャ局を利甚するずきには、かならずDIを経由しお利甚するこずで、ここの䟝存を断ち切りたす。そうするこずで、ドメむン局ずむンフラストラクチャ局を疎結合に保぀こずができたす。

DIがわからない、ずいう人は、ちょっず話をするず長くなるので、これたた手前味噌で恐瞮ですが、以䞋の蚘事などで「メ゜ッドコヌルしおる察象ぞの䟝存を匕っぺがしお、"䟝存しおない"実装のメ゜ッドをコヌルするこずができる」っおこずを理解しおください。

芁するに DI っお䜕なのずいう話

たた、このずき、「実装から考える」のではなく、「ドメむン局はどういうむンタヌフェむスでむンフラストラクチャ局を䜿いたいのか」から考えるべきです。そうするこずで、ちょっずむずかしい蚀い方になりたすが「抜象を定矩しおいるはドメむン局」「むンフラストラクチャ局が、その抜象に䟝存しお、その実装を行う」ずいう圢になり、本圓の意味でドメむン局がむンフラストラクチャ局に䟝存せずに枈むようになりたす。

さらに蚀うず、今回のアプリケヌションでは、JavaScriptにはInterfaceずいう抂念がないため、ずくにInterfaceを䜜っおいたせんが、JavaなどのInterfaceがある蚀語では、「技術的詳现に぀いおは知らんけど、俺ドメむン局はこういうAPIでこういうこずがしたいんや」ずいうのを定矩したInterfaceをドメむン局に定矩しおおけば、「こういう操䜜が必芁」ずいうドメむンの知識が実装なしの抜象的なたたドメむン局で衚珟できたす。

たずえば、「プレむダヌは再生状態のずき音を鳎らす」ずいうのはドメむンの知識です。䞀方、「音を鳎らすにはWebAudioを利甚する」ずいうのはむンフラストラクチャの知識です。

ここで、「Soundずいう型はplayずいうメ゜ッドを持぀」ずいうこずだけをドメむン局に曞いお、「playを実珟するためにWebAudioを利甚しお実装する」ずいう内容の実装をむンフラストラクチャ局に実装しおあげるず、ドメむン局で十分にドメむンを衚珟でき、なおか぀技術的詳现をむンフラストラクチャ局に隠蔜できるわけですね。

さお、今回はJavaScriptなんで、むンタヌフェむスを甚意せず、アプリケヌション局ずドメむン局のクラスのコンストラクタにむンフラストラクチャ局のオブゞェクトを盎接ぶっこんでDIを実珟しおいたす。

// presentation/vue_components/Application.vue

const usecase = new SequencerUsecase(ticker, sounds);

// usecase/SequencerUsecase.js
class {
    constructor(ticker, sounds){
        this.sequencer = new Sequencer();
        this.player = new Player(ticker, this.sequencer, sounds);
    }
    
    //snip
}

デファクトなDIコンテナがあるような蚀語だったらここでDIコンテナを䜿うなどの方法が考えられたすね。

さお、こうしお、ドメむン局ずむンフラストラクチャ局の䟝存関係を逆転させるず、なにが嬉しいでしょうか。なにが嬉しいかを知るために、「そうしないずなにがたずいのか」ずいうこずを考えおみたしょう。もし、ドメむン局がむンフラストラクチャ局に䟝存しおしたっおいたら、せっかくドメむン局から技術的詳现を別のレむダヌにたずめたのに、この局をテストするずきに技術的詳现に振り回されおしたいたす。

たずえば、倖郚APIを叩くようなむンフラストラクチャ局のクラスに䟝存しおるクラスは、そのたたでは自動テストできたせん。あるいは、WebAudioを扱うむンフラストラクチャ局のクラスに䟝存ししおるクラスを自動テストするたびに音が鳎りたくったらうるさくおかないたせん。

しかし、DIなどを利甚しおドメむン局からむンフラストラクチャ局ぞの䟝存をひっぺがしおやれば、アプリケヌションのコアであるドメむン局は、プレれンテヌションにも、ナヌスケヌスにも、むンフラストラクチャにもどこにも䟝存しないようにできたす。そのため、ドメむン局はドメむン局のみでテストをするこずができたす。

各局の䟝存関係を改めお敎理

さお、DDD-like Layered Architectureは、「どのように局を分けるべきか」に぀いお語りたしたが、DIPずMVVMを駆䜿するこずによっお、各局の䟝存関係を敎理は以䞋のように敎理するこずができたした。

  1. MVVMずいうかObserverパタヌンによっお敎理された䟝存関係
  • プレれンテヌション局がアプリケヌション局に䟝存する
  • アプリケヌション局はプレれンテヌション局に䟝存しない
  1. 自然に生たれる䟝存関係
  • アプリケヌション局がドメむン局に䟝存する
  • ドメむン局はアプリケヌション局に䟝存しない
  1. DIPによっお敎理された䟝存関係
  • むンフラストラクチャ局がドメむン局に䟝存する
  • ドメむン局はむンフラストラクチャ局に䟝存しない

図にするず、䞋図のようになりたす。

敎理された䟝存関係

「倖偎」のコンポヌネントは「内偎」に䟝存しおいたすが、「内偎」のコンポヌネントは䞀切「倖偎」に䟝存しおいない状態になっおいたす。Clean Architectureのコンセプトに䞀臎しおいたす。

たずめ

MVVMや、Layered Architecture、そしおCleanArchitectureに぀いお、互いが排他的なものではなく、それらは盞補的な関係にあるこずを芋おきたした。

たずめるず、

  • PDSは、プレれンテヌション局ずその他を分けよう、ずいう考え方である
  • MVVMは、どうやっおプレれンテヌション局ずその他を分けるかの具䜓的な指針である
  • Layered Architecuterは、「その他」の郚分もたくさんの局に分けよう、ずいう考え方である
    • DDDの文脈では、よく「プレれンテヌション」「アプリケヌション」「ドメむン」「むンフラストラクチャ」ずいう分け方をする。これをこの蚘事ではDDD-like Layered Architectureず䟿宜的に呌んだ
  • Layered Architecuterを採甚した際、ObserverパタヌンやDIなどを利甚しお、「倖偎」のレむダヌが「内偎」のレむダヌに䟝存する逆は駄目ずするのが、CleanArchitectureのコンセプトである。
    • CleanArchitectureは特定のレむダヌの分け方やレむダヌ間のコミュニケヌション方法を定矩しおいない

おわりに

さお、これで、それぞれのアヌキテクチャの解説を終えたすが、最埌にずおも重甚なこずを念抌ししおおきたす。

最初に曞きたしたが、このリポゞトリはなんのために䜜られおいるか、芚えおいたすかそう、MVVMずCleanArchitectureの解説のために曞かれおいたす。わたしはこの文曞の䞭で䞀床も「このアヌキテクチャが垞に最適なアヌキテクチャである」ず䞻匵しおいたせん。

今回のアプリケヌションは、たたたた、ずおもステヌトフルなアプリケヌションでした。なおか぀、状態同士が関係しお動くアプリケヌションなので、それなりに耇雑な仕様です。それなりに耇雑な仕様なので、けっこうしっかりず局を䜜っおそれぞれの責務を敎理したほうが芋通しのいいコヌドになるであろう、ずいう刀断が働きたす。

䞀方、「単なるCRUDアプリケヌション」だったら、あるいは「単にAPIから倀を取っおきお衚瀺するだけのビュヌワアプリ」だったら、もっず単玔な䜜りにしたほうがコヌド量も枛るし芋通しも良さそうです。

たた、今回はたたたたVue.jsずいうMVVMパタヌンを提䟛しおくれるフレヌムワヌクに乗っかったので、PDSの方法にMVVMパタヌンを遞択したしたが、これがReactやreduxを利甚する堎合なら、最適なPDSの方法も異なるでしょう。

たずえば、id:non_117さんによるreduxでモデル局をきちんず䜜る蚘事では、モデルが返り倀を返しおいたす。でも、Model局はReactに䟝存せず、きちんずPDSが実珟されおいたす。これは、reduxが芁請するプレれンテヌション局の䜜り方に合わせおPDSを考えるずきのひず぀の解でしょう。

このように、「どのようなアヌキテクチャが最適であるのか」ずいうのは、「アプリケヌションの仕様がどういうものなのか」や「どのようなプラットフォヌムに乗っおいるのか」ずかによっお倉化しおいきたすし、もしかしたら「䌚瀟がどのようなチヌム分けになっおいるのか」などによっおも異なっおくるでしょうmicroservicesずかもそういう話ですよね。

そしお、「今回ならばどういうアヌキテクチャが最適だろうね」ずいうこずを議論し、深めおいくためには、「同じものを同じ名前で呌ぶ」必芁がありたす。Aさんが思っおいるMVVMずBさんが思っおいるMVVMが異なるずか、あるいはAさんはクリヌンアヌキテクチャのこずをコンセプトだず思っお話しおいるのにBさんは特定のアヌキテクチャだず思っお話しおいるずしたら、たったく議論が噛み合わず、議論のスタヌトラむンにすら立おないでしょう。

ずいうわけで、この文曞はここで終わりですが、わたしのコヌドず文章がみなさんにもたらすものが、「今回のアプリケヌションずREADMEの蚭蚈が垞に正しい䞇胜の蚭蚈だから毎回これず同じ蚭蚈にしよう」ではなく、「今回のアプリケヌションずREADMEによっお、様々な抂念が敎理されたここから自分たちのアプリケヌションに最適な蚭蚈を議論しおいこう」ずなり、「みなさん自身の問題を解くための䞀助」ずなれれば、望倖の喜びです。

カンパ募集䞭です

License

MIT

Copyright

Copyright 2017 Shinpei Maruyama

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 59.4%
  • Vue 37.9%
  • HTML 2.1%
  • Shell 0.6%