実践 SAPE アーキテクチャ

# :necktie: SAPE アーキテクチャ

*SAPE* (サップ) アーキテクチャは, *S*, *A*, *P*, *E* の各層に分けて書くソフトウェアアーキテクチャ及び開発手法だよ.

がんばって私が考えました, えっへん.

# :cake: 各層の役割

以下からは各層の役割の解説をしていく. 実際のアプリ開発では, どの層から書き始めてもまんべんなくすべての層を書き上げる感じになるよ.

## :baseball: Exp(ression)

*Exp* では, このアプリで登場する **データ構造と, その制約を維持しつつ操作する関数群** を書くよ.

<details><summary>Rust でのコード例: 電卓なら, 数と演算子の定義, 計算木構造の定義, 木構造から数値を算出する関数, その単体テストなどをここに置く.</summary><div>

```rust:src/exp.rs
#[derive(Debug, Clone, Copy)]
pub enum BinaryOp {
  Add,
  Sub,
  Mul,
  Div,
}

#[derive(Debug, Clone)]
pub enum Term {
  Num(f64),
  Binary(Box<Term>, BinaryOp, Box<Term>),
}

impl Term {
  pub fn calc(&self) -> f64 {
    use BinaryOp::*;
    use Term::*;
    match &self {
      Num(num) => *num,
      Binary(left, Add, right) => left.calc() + right.calc(),
      Binary(left, Sub, right) => left.calc() - right.calc(),
      Binary(left, Mul, right) => left.calc() * right.calc(),
      Binary(left, Div, right) => left.calc() / right.calc(),
    }
  }
}

// 後略
```

</div></details>

*Exp* に置いたコードは, ***Exp* 内の他コードと基本データ型にしか依存してはいけない** よ.

`class` が書けるオブジェクト指向プログラミング言語ならそれを使ったほうがいいね.

## :family: Play

*Play* では, この **アプリ自体がやる仕事のアルゴリズム** とそのエラー系を書くよ.

<details><summary>Rust でのコード例: 電卓なら,「数や演算子の入力を計算して出力する」処理をここに置く.</summary><div>

```rust:src/play.rs
use crate::abst::{Controller, Input, Presenter};
use crate::exp::{BinaryOp, SyntaxError, Term};

pub fn calculate(controller: &impl Controller, presenter: &impl Presenter) {
  let mut inputs = controller.get_inputs();
  match parse_add_sub(&mut inputs) {
    Ok((term, _)) => presenter.show_result(term.calc()),
    Err(err) => presenter.show_error(err),
  }
}

type ParseResult<'a> = Result<(Term, &'a [Input]), SyntaxError>;

/*
  AddSub :=
    | MulDiv + MulDiv
    | MulDiv - MulDiv
    | MulDiv
  MulDiv :=
    | Factor * Factor
    | Factor / Factor
    | Factor
  Factor :=
    | LParen AddSub RParen
    | Num
*/

fn parse_add_sub(tokens: &[Input]) -> ParseResult {
  let (mut term, mut tokens) = parse_mul_div(tokens)?;
  loop {
    match tokens {
      [Input::Plus, rest @ ..] => {
        let (right, rest) = parse_mul_div(rest)?;
        term = Term::Binary(term.into(), BinaryOp::Add, right.into());
        tokens = rest;
      }
      [Input::Minus, rest @ ..] => {
        let (right, rest) = parse_mul_div(rest)?;
        term = Term::Binary(term.into(), BinaryOp::Sub, right.into());
        tokens = rest;
      }
      _ => break Ok((term, tokens)),
    }
  }
}

fn parse_mul_div(tokens: &[Input]) -> ParseResult {
  let (mut term, mut tokens) = parse_factor(tokens)?;
  loop {
    match tokens {
      [Input::Cross, rest @ ..] => {
        let (right, rest) = parse_factor(rest)?;
        term = Term::Binary(term.into(), BinaryOp::Mul, right.into());
        tokens = rest;
      }
      [Input::Division, rest @ ..] => {
        let (right, rest) = parse_factor(rest)?;
        term = Term::Binary(term.into(), BinaryOp::Div, right.into());
        tokens = rest;
      }
      _ => break Ok((term, tokens)),
    }
  }
}

fn parse_factor(tokens: &[Input]) -> ParseResult {
  match tokens {
    [Input::Num(n), ..] => {
      return Ok((Term::Num(*n), &tokens[1..]));
    }
    [Input::LParen, rest @ ..] => {
      let (term, rest) = parse_add_sub(rest)?;
      if let [Input::RParen, ..] = rest {
        Ok((term, &rest[1..]))
      } else {
        Err(SyntaxError)
      }
    }
    _ => parse_add_sub(tokens),
  }
}
```

</div></details>

*Play* に置いたコードは, **参照透過な関数しか作ってはいけない** よ. インターネットや DB や UI といった外部は, *Abst* で抽象化して引数からもらうようにするよ. こうすることで, **テスト性が向上** する.

*Play* は *Exp* と *Abst* に依存しているからこそ, *Play* が **書きやすくなるように, *Exp* と *Abst* のコードをリファクタしていく** ようにね.

## :cloud: Abst(ract)

*Abst* では, ***Exp* が使いたい外部を抽象化して定義** したり, それを合成/自動生成するようなコードを書くよ.

<details><summary>Rust でのコード例: 電卓なら, 外部からの入力と出力の `trait` とそれ専用のデータ構造をここに置く.</summary><div>

```rust:src/abst.rs
use crate::exp::SyntaxError;

#[derive(Debug, Clone)]
pub enum Input {
  Plus,
  Minus,
  Cross,
  Division,
  LParen,
  RParen,
  Num(f64),
}

pub trait Controller {
  fn get_inputs(&self) -> Vec<Input>;
}

pub trait Presenter {
  fn show_error(&self, error: SyntaxError);
  fn show_result(&self, calculated: f64);
}
```

</div></details>


*Abst* に置いたコードは, ***Abst* 内の他コードか *Exp* にしか依存してはいけない** よ.

## :alien: Skin

*Skin* では, ***Abst* に対する実装** として外部の API やフレームワークを直接触るコードを書くよ.

<details><summary>Rust でのコード例: 電卓なら, CUI や GUI とのやり取りをここに置く.</summary><div>

```rust:src/skin/console.rs
use crate::abst::{Controller, Input, Presenter};
use crate::exp::SyntaxError;

pub struct Console;

fn string_to_tokens(string: &[char]) -> Vec<Input> {
  use Input::*;

  let mut tokens = vec![];
  let mut num_buf = String::new();
  for raw_token in string {
    if raw_token.is_digit(10) || raw_token == &'.' {
      num_buf.push(*raw_token);
      continue;
    }
    if !num_buf.is_empty() {
      tokens.push(Num(num_buf.parse().unwrap()));
      num_buf.clear();
    }
    tokens.push(match raw_token {
      '+' => Plus,
      '-' => Minus,
      '*' => Cross,
      '/' => Division,
      '(' => LParen,
      ')' => RParen,
      ' ' => continue,
      _ => unimplemented!("Unimplemented token: {}", raw_token),
    });
  }
  if !num_buf.is_empty() {
    tokens.push(Num(num_buf.parse().unwrap()));
    num_buf.clear();
  }
  tokens
}

impl Controller for Console {
  fn get_inputs(&self) -> Vec<Input> {
    println!("Input the expression:");

    let mut buffer = String::new();
    let stdin = std::io::stdin();
    stdin.read_line(&mut buffer).expect("no input");

    let raw_tokens: Vec<_> = buffer.trim().chars().collect();
    string_to_tokens(&raw_tokens)
  }
}

impl Presenter for Console {
  fn show_error(&self, _: SyntaxError) {
    println!("Syntax error");
  }

  fn show_result(&self, result: f64) {
    println!("{}", result);
  }
}
```

</div></details>


*Skin* に置いたコードは, ***Abst* の実装以外のことをしてはいけない** よ. アプリケーションで必要なロジックは *Skin* に紛れ込ませずに *Play* や *Exp* に分けよう.

## :notepad_spiral: まとめ

- *Skin*
- *Abst*
- *Play*
- *Exp*

下図に層のコードの関係を表しといたよ.

![concept_diagram](http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuKfCJyqhKGZEpimpH68R8R6DkJBpIbABqXAJKnKSaufB3D7uHbobnQd9YSMf2W4vYPM6A3oOEQuL1OO8fX4N5qX0mKPN5we798ZWmcMXPe1jHP86suh1HwW7-qP1LxV63k9WT0682YDq5SR8AXnIyrA0uGC0)

Rust のサンプルコードたちは, [この sape_sample レポジトリ](https://github.com/approvers/sape_sample) に全文載せてあるから気になるなら見ていってね.

# :heart_exclamation: 存在意義

これは, [The Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) を削ぎ落としてもっと効率的にしようと考えて作ったよ. また, Onion Architecture や DDD からも考え方を借りてる.

おかげで, 以下のような強みを得られた.

## :shield: 外部の変更から守る

さっきの図でも分かる通り, すべての依存の矢印がシステムの内側に向いている. であるから, 外部の変更があっても *Skin* の修正だけで済み, 他のコードに影響しない.

いわゆる DIP (依存関係の逆転の原則) が適用されている.

## :green_heart: テストを作る粒度が明確

*Exp* が独立した純粋人工物になるようにしているから, そこに単体テストを書くしそれ自体の使い方も分かりやすくなる. また, *Exp* に単体テストを書くので, *Exp* 自体のバグをできるだけ排除できる.

反対に, 結合テストは *Play* に対して行う. *Abst* を実装したモックを *Skin* に置いておけば, あとはなんらかのテストフレームワークなどを使って楽に結合テストが書ける.

単体テストやモックのまとめ方が定義されているから, テスト目的のファイル配置が人によって変わったりしないのでプロジェクト運用が効率的になる.

## :file_folder: モジュール = フォルダの階層

同様に, 再配置/追加した機能の場所に迷うこともない. プログラムの役割ごとに配置するモジュール名とディレクトリが明確なので, いちいち責務から配置を考えたりしなくていい.

プログラムの階層名とフォルダの名前が同じになるので, あとから見直したり探したりしやすくなる.

## :construction_site: 完全に独立したアーキテクチャ

このアーキテクチャをプロジェクト全体に渡って採用する必要はない. 小さなモジュール単位でこのやり方を実践するだけでも, 恩恵が受けられると思う.

このアーキテクチャの中にこのアーキテクチャを採用した... というような階層構造を作っても問題ない. このアーキテクチャで組んだモジュールをこのアーキテクチャで再利用する場合は, *Exp* の中に入れるようにね.

- `abst`
- `exp`
  - `another`
     - `abst`
     - `exp`
         - :
     - `play`
     - `skin`
  - : 
- `play`
- `skin`

---

# :end: おわりに

お手軽なアーキテクチャだと思うので, ぜひ試していってね. ここまで読んでくれてありがと.