Posted at

Web FrontendでClean Architectureを試す

こんにちは、スープです。

EdTechビジネスのプラットフォームチームでエンジニアをしています。

Clean ArchitectureでTodoアプリのFrontendを試作しました。

良ければ実装をみてください。

https://github.com/souppower/clean-architecture-todo-frontend


概要

Clean Architectureでは、UIやDB、FWといったアプリケーションの詳細を、ビジネスルールから切り離し、プラグインとして実装します。

これには、テスタビリティやコードのリーダビリティが高く保たれる等のメリットがあります。

とはいえ最大のメリットは、詳細の決定を遅延できることかもしれません。


詳細の決定を遅延できるということ

開発初期には、すべてのユースケースを把握できないため、重大な決定は遅延できるのが望ましいです。

開発が進むにつれて、適切な決定を下すための情報が数多く手に入るため、後回しにできるメリットは大きい。

また、数多くの実験をする余裕を獲得するというメリットもあります。

このメリットについては、Robert C. Martin 氏が度々強調しています。

https://blog.cleancoder.com/uncle-bob/2011/11/22/Clean-Architecture.html


実装

TODOアプリの実装を手短に見ていきます。


Domain

TODOの型定義があるだけです。

export interface Todo {

id: number;
title: string;
}


Usecase

アプリケーション固有のビジネスルールを書いています。

渡された todoRepo にデータのやりとりの処理を委譲します。

特定の実装に依存せず、interfaceに依存させることにより、ビジネスルールが外部の詳細を知らずにすみます。

import { Todo } from "domain";

import { TodoRepository } from "./repository";

export default class TodoUsecase {
constructor(private todoRepo: TodoRepository) {}

findAll(): Todo[] {
return this.todoRepo.findAll();
}

add(text: string) {
this.todoRepo.add(text);
}

edit(id: string, text: string) {
this.todoRepo.edit(id, text);
}

finish(id: string) {
this.todoRepo.delete(id);
}

finishAll() {
this.todoRepo.deleteAll();
}
}


Interface


Presenter

TODOアプリケーションの表示部分を司ります。

import React from "react";

import Usecase from "usecase/todo";
import { Todo } from "domain";

import { Input, Item, ClearButton } from "./todos";

import "./app.css";

interface Props {
usecase: Usecase;
}

interface State {
input: string;
todos: Todo[];
}

export default class App extends React.Component<Props, State> {
private usecase: Usecase;

constructor(props: Props) {
super(props);
this.usecase = this.props.usecase;
this.state = {
input: "",
todos: []
};
}

componentDidMount() {
const todos = this.usecase.findAll();
this.setState({ todos });
}

handleChange(e: any) {
this.setState({ input: e.target.value });
}

onKeyPress(e: any) {
if (e.key !== "Enter") {
return;
}

const input = e.target.value;
this.usecase.add(input);
this.setState({ input: "" });
const todos = this.usecase.findAll();
this.setState({ todos });
}

finishTodo(id: string) {
this.usecase.finish(id);
const todos = this.usecase.findAll();
this.setState({ todos });
}

clearTodos() {
this.usecase.finishAll();
this.setState({ todos: [] });
}

render() {
const { todos } = this.state;
return (
<section className="todoapp">
<Input
value={this.state.input}
handleChange={this.handleChange.bind(this)}
onKeyPress={this.onKeyPress.bind(this)}
/>

<section>
<ul className="list-group">
{this.state.todos.map(todo => (
<Item
key={todo.id}
todo={todo}
onFinish={this.finishTodo.bind(this)}
/>
))}
</ul>
{todos.length > 0 && (
<div className="right-align">
<ClearButton onClick={this.clearTodos.bind(this)} />
</div>
)}
</section>
</section>
);
}
}


Infrastructure

TODOの保存処理を書きました。

Memory か LocalStorage 保存か選択できるように、 store は interface に依存させ、詳細ロジックはDIしてもらうようにしました。

import Persistor from "interface/repository/persistor";

export default class Store {
constructor(private store: Persistor) {}

get(key: string) {
return this.store.get(key);
}

getAll() {
return this.store.getAll();
}

save(value: any) {
this.store.save(value);
}

delete(id: string) {
this.store.delete(id);
}

clear() {
this.store.clear();
}
}


エントリーポイント

そして最後にエントリーポイントです。

usecase を組み立てて、 Presenter に渡しています。

import React from "react";

import ReactDOM from "react-dom";

import { LocalStorage, Persistor } from "infrastructure";
import { TodoRepository } from "interface/repository";
import App from "interface/presenter";
import { TodoUsecase } from "usecase";

const storage = new LocalStorage("clean-architecure-todo");
const persistor = new Persistor(storage);
const todoRepository = new TodoRepository(persistor);
const usecase = new TodoUsecase(todoRepository);

ReactDOM.render(<App usecase={usecase} />, document.getElementById("app"));


感想

これからもやっていきたい。


参考

Front End Architecture — Making rebuild from scratch not so painful

JavaScriptでClean Architectureを導入してみた - Vue.js・Reactのサンプルつき

Clean Architecture

Clean Architecture in Web Frontend #mixleap