0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Antigravityを使ったClojure/ClojureScriptによるフルスタックWeb開発

0
Posted at

Antigravityを使ったClojure/ClojureScriptによるフルスタックWeb開発

近年、フロントエンドの複雑化に伴いReactを利用した開発が主流となっている。でもほとんどがPythonやTypeScriptで開発されている。
その中で筆者はLisp系の言語であるClojureおよびClojureScriptを用いたいと考えていた。Clojure/ClojureScriptでは強力な状態管理とREPL駆動開発による高速なフィードバックループを実現できる。
そこで、現在使っているAntigravityを用いて作ってみた。
本記事では、Mac環境での開発環境セットアップから、shadow-cljsReagent(Reactラッパー)、RingベースのAPIサーバー、そしてSQLiteを用いた簡単なログインアプリケーション構築までのフルスタックな手順をまとめる。

AIコーディング時代におけるClojureの圧倒的な優位性

特に昨今、AIによるコード生成やペアプログラミングアシスタントが当たり前の時代になりつつある。TypeScriptやPythonといった他言語と比較した際、実はClojureはAIコーディングと最も相性が良い言語の一つであるといえる。その理由は明確に4つある。

  1. 同図像性 (Homoiconicity) とシンプルな構文による推論のしやすさ
    Lisp系言語であるClojureのコードは、すべてClojure自身のデータ構造(リスト)として表現される。複雑な制御構文や独自のシンタックス・シュガーが少なく、括弧の中に「関数と引数」が並ぶだけのシンプルな構造(S式)であるため、LLM(大規模言語モデル)にとってコードの抽象構文木をそのままパースしているのと同義になり、全体構造の把握やロジック書き換えの精度が他言語に比べて非常に高くなる。

    1. イミュータブルと純粋関数による安全なコード生成
      Clojureは不変なデータ構造と副作用のない純粋関数による設計が基本となる。そのため、AIに「この入力マップを受けてこういう形に変換する関数を作って」と依頼するだけで、外部のグローバルな状態などを破壊する心配のない、安全でそのまま使える「部品」が高確率で出力される。
      これはJavaのようなオブジェクト指向言語とは対照的である。 オブジェクト指向言語は状態をオブジェクトに隠蔽するため、AIはコードの構造を把握するために多くのコンテキストを必要とする。そのためハルシネーションが起きやすいと考える。
  2. データ指向 (Data-Driven)
    複雑なクラスやオブジェクトモデルを設計せず、主に「マップ(連想配列)」と「ベクター」というシンプルな基本データ構造だけで全てをやり取りする。そのため、AIに対して膨大なクラス定義や型情報のコンテキストを与える必要がなく、JSONを扱うような感覚で直感的に指示が通る。

  3. REPLによる「AI出力の1秒検証」
    AIが生成したコードが正しいかどうかは、結局動かしてみるまで分からない。ClojureのREPL駆動開発(エディタ上のインライン評価機能)を使えば、AIがエディタ上に書き出した関数にカーソルを合わせ、ショートカット(Alt+Enter など)を押すだけで、その場ですぐにテスト実行できる。ブラウザのリロードやサーバーの再起動を待つことなく「AIに書かせる → 即評価して確認する」というラリーが爆速で回せる。

本記事の環境構築を通し、この強力な「Clojure × REPL × AI」のシナジーをぜひ体感してほしい。

1. 開発環境のセットアップ (Mac版)

Clojure開発においてデファクトスタンダードとなっているのは、VS Codeと拡張プラグイン「Calva」の組み合わせである。
まずは、ターミナルからHomebrew経由で必要なシステムツールをインストールする。

# 1. Java (OpenJDK) のインストール
brew install openjdk@17
sudo ln -sfn /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk

# 2. Clojure CLI ツール (clojure / clj) のインストール
brew install clojure/tools/clojure

# 3. Node.js のインストール (ClojureScript/shadow-cljs のビルドに必須)
brew install node

次に、VS Code 側で快適に Clojure を書くために以下の拡張機能をインストールする。

  1. Calva (Clojure & ClojureScript Interactive Programming): シンタックスハイライト、コードフォーマット、インラインでの評価、REPL統合を提供する必須プラグイン。
  2. clj-kondo (Linter): 強力な静的解析ツール。
  3. Parinfer / Paredit: 括弧のバランスを自動調整し、Lispらしい構造編集を可能にする機能(Calvaに標準搭載)。

2. プロジェクトの基本構造と構成図

本記事で作成するフルスタックアプリケーションは、主に以下の技術スタックとライブラリ構成で動作する。
バックエンドからフロントエンドまで、すべてをClojureのエコシステムで統一している。

[ ブラウザ (Client) ]
      │
      ├─ UI描画: Reagent (React Wrapper)
      ├─ 状態管理: reagent.core/atom
      └─ HTTP通信: cljs-ajax (POST / GET)
      │
( JSON Request / Response )
      │
[ サーバー (Host / shadow-cljs dev-http) ]
      │
      ├─ Webサーバー/ミドルウェア: Ring + ring-json + ring-devel
      ├─ ルーティング: Compojure (defroutes)
      └─ DBアクセス: next.jdbc
      │
[ データベース ]
      └─ SQLite (users.db)

ビルドツールには、ClojureScriptの環境構築を圧倒的に容易にする shadow-cljs を採用する。

shadow-cljs.edn

パッケージの依存関係と開発サーバーの設定を行う。dev-http 内でClojure側(バックエンド)のRingハンドラを呼び出す設定にすることで、1つのポート(プロセス)でAPIとフロントエンド配信を兼ねることができる。

{:source-paths ["src"]
 :dependencies [[reagent "1.2.0"]
                [com.github.seancorfield/next.jdbc "1.3.939"]
                [org.xerial/sqlite-jdbc "3.46.0.0"]
                [ring/ring-core "1.12.2"]
                [ring/ring-json "0.5.1"]
                [ring/ring-devel "1.12.2"]
                [compojure "1.7.0"]
                [cljs-ajax "0.8.4"]]
 :dev-http {8080 {:root "public" :handler backend.core/app-handler}}
 :builds
 {:app {:target :browser
        :output-dir "public/js"
        :asset-path "/js"
        :modules {:main {:init-fn app.core/init}}
        :devtools {:after-load app.core/reload}}}}

パッケージのインストール(npm install)後、以下のコマンドを実行することでビルドサーバーが立ち上がる。

npx shadow-cljs watch app

3. まずはシンプルなHot Reloadアプリから

ソースコード /src/app/core.cljs にReactコンポーネントを定義する。
Reagentでは、HTMLライクなツリー構造を配列リテラル(Hiccup記法)で表現する。

(ns app.core
  (:require [reagent.dom :as rdom]
            [reagent.core :as r]))

(defonce state (r/atom {:text "Hello, ClojureScript!"}))

(defn hello-component []
  [:div 
    [:h1 (:text @state)]
    [:button {:on-click #(swap! state assoc :text "Hot Reload works!")}
     "Click me"]])

(defn ^:dev/after-load reload []
  (rdom/render [hello-component]
               (js/document.getElementById "app")))

(defn ^:export init []
  (reload))

この状態でファイルを保存すると、ブラウザを手動で更新することなく、現在の状態(State)を維持したまま画面が即座に変更される。

4. SPAとログインAPIの実装

続いて、バックエンド(API)とフロントエンドが連携する実践的なログイン機能を持つSPA(Single Page Application)を構築する。

今回のアプリケーションは、それぞれ独立した役割を持つたった2つの主要ファイルだけで構成される、非常に見通しの良いアーキテクチャとなっている。

  • src/backend/core.clj (サーバー側: Clojure)
    このファイルはバックエンドとして振る舞う。RingとCompojureを用いてWebサーバーやAPIエンドポイント(POST /api/login など)を提供し、next.jdbcを経由してSQLiteデータベースへアクセスし、パスワードの照合やユーザー情報の永続化を担当する。
  • src/app/core.cljs (ブラウザ側: ClojureScript)
    このファイルはクライアントサイド(フロントエンド)として実行される。Reagentを用いてログインフォームやホーム画面などのUIコンポーネントを描画するほか、単一のアトム(state)で「現在の表示ページや入力中のメールアドレス」等の状態を管理し、ユーザーのボタン操作に応じてバックエンド(core.clj)へAPIリクエスト(Ajax通信)を送信する役割を担う。

バックエンド API 実装 (core.clj の解説)

サーバー側のエントリポイントとなる src/backend/core.clj に、SQLiteとの接続と機能を記述する。

(ns backend.core
  (:require [next.jdbc :as jdbc]
            [next.jdbc.sql :as sql]
            [compojure.core :refer [defroutes POST GET]]
            [compojure.route :as route]
            [ring.middleware.json :refer [wrap-json-response wrap-json-body]]
            [ring.middleware.reload :refer [wrap-reload]]))

;; 1. データベース接続設定 (SQLite)
(def db-spec {:dbtype "sqlite" :dbname "users.db"})

;; 2. ログイン処理のロジック
(defn login [{:keys [email password]}]
  ;; next.jdbc.sql を使ってユーザーを検索
  (let [user (first (sql/find-by-keys db-spec :users {:email email :password password}))]
    (if user
       {:status 200 :body {:success true}}
       {:status 401 :body {:success false :message "Unauthorized"}})))

;; 3. APIエンドポイントのルーティング定義
(defroutes app-routes
  (GET "/" [] {:status 200 :headers {"Content-Type" "text/html"} :body (slurp "public/index.html")})
  (POST "/api/login" request (login (:body request))))

;; 4. アプリケーションのエントリポイント (ミドルウェアの適用)
(def app-handler
  (-> app-routes
      (wrap-json-body {:keywords? true})
      wrap-json-response
      (wrap-reload {:dirs ["src/backend"]})))

【コードのポイント】

  1. DBアクセス: next.jdbc ライブラリを用いている。sql/find-by-keys などのヘルパー関数を使うことで、SQL文を直接書かずともClojureのMap({:email email :password password})を渡すだけで直感的にレコードを検索できる。
  2. ルーティングとJSON変換: Compojuredefroutes でURLを定義し、そこにRingミドルウェアである wrap-json-bodywrap-json-response をスレッドマクロ(->)でパイプライン的に適用している。これにより、リクエスト受け取り時のJSONパースや、レスポンス返却時のJSON文字列化をすべて自動的に行っている。
  3. Hot Reload: 一番下に wrap-reload を挟むことで、フロントエンドだけでなくバックエンドのClojureコードも変更するたびにサーバーを再起動することなく、動的に反映させることができる。

フロントエンド画面と通信の実装 (core.cljs の解説)

次に、ブラウザ側で動作する src/app/core.cljs を実装する。ここでは UI コンポーネントと、先ほど作ったAPIを呼び出す通信処理をまとめる。

(ns app.core
  (:require [reagent.dom :as rdom]
            [reagent.core :as r]
            [ajax.core :refer [POST]]))

;; 1. グローバルな状態管理 (Atom)
(defonce state (r/atom {:current-page :login
                        :email ""
                        :password ""
                        :error-message nil}))

;; 2. バックエンドAPIへのAjaxリクエスト
(defn handle-login []
  (let [{:keys [email password]} @state]
    (POST "/api/login"
      {:params {:email email :password password}
       :format :json
       :response-format :json
       :keywords? true
       :handler (fn [response]
                  (if (:success response)
                    (swap! state assoc :current-page :home :error-message nil)
                    (swap! state assoc :error-message (:message response))))})))

;; 3. Reagent (React) UIコンポーネント
(defn login-component []
  (let [{:keys [email password error-message]} @state]
    [:div.login-container
     [:h1 "ログイン"]
     (when error-message [:div.error error-message])
     ;; 入力フォームの状態をAtomと同期
     [:input {:type "email" :value email
              :on-change #(swap! state assoc :email (.. % -target -value))}]
     [:input {:type "password" :value password
              :on-change #(swap! state assoc :password (.. % -target -value))}]
     [:button {:on-click handle-login} "送信"]]))

;; 省略: 画面切り替えのルーティング(main-component)や初期化処理...

【コードのポイント】

  1. 状態管理の圧倒的なシンプルさ: React における useState や Redux のような複雑な仕組みは使わず、Clojureに標準で備わっているAtom(変更可能な参照型変数)を一つ用意するだけで完結している。
  2. 双方向バインディング (Hiccup記法): [:input]on-change イベント内で (swap! state assoc ...) と書くことで、入力された文字を即座に Atom に反映させている。値が更新されると、Reagent が自動的に画面(UI)を再レンダリングする構造になっている。
  3. Ajax通信: cljs-ajaxPOST でバックエンドへ通信し、成功・失敗時のレスポンスに応じて再び swap! を使い表示ページ(:current-page)やエラーメッセージを上書きしている。

5. REPL 駆動開発 (REPL-Driven Development) の体験

開発環境が整ったら、VS Code の Calva を使って「REPL 駆動開発」を行う。これが Clojure 最大の強みである。

  1. コマンドパレット (Cmd + Shift + P) から Calva: Start a Project REPL and Connect (aka Jack-in) を実行。
  2. shadow-cljs -> :app を選択してビルドに接続する。(※ブラウザでアプリを開いていることが条件)

接続されると、エディタ上のソースコードを直接評価できるようになる。
コードの末尾にカーソルを合わせ Alt+Enter(S式全体の評価)または Ctrl+Enter を押下するだけで、その関数の実行結果がエディタの右側にインラインでポップアップ表示される。

わざわざターミナルで実行したりログを仕込んだりしなくても、エディタ上で関数の挙動を直接テストしながら実装を進めることが可能だ。

alt text

6. AIコーディングアシスタント「Antigravity」を活用した開発プロセス

本記事で紹介した開発環境のセットアップからアプリケーションの構築に至るまで、その大半の作業は Google DeepMind のAIエージェント「Antigravity」とのペアプログラミングによって行われた。

Clojure開発におけるAntigravityの活用方法は以下の通りである。

  1. 環境構築とアーキテクチャ定義の自動化
    「Mac環境でClojureのフルスタック環境を作って」というラフな指示に対し、AntigravityはHomebrewを使ったツールのインストール提案から、ベストプラクティスに基づいた shadow-cljs.edn の生成までを一手に担った。さらに「標準アーキテクチャ」を自動で言語化し、Markdownとして保存することでその後の開発方針のブレを防いだ。
  2. ルーティングからDB設計までの一括構築
    「SQLiteでログイン機能を実装したい」という指示のもと、next.jdbc を使ったDB接続、Ring/CompojureによるサーバーサイドのAPI実装、Ajaxによるフロントエンドの通信処理を数秒で連携・生成。面倒なボイラープレート(定型コード)を書く手間が完全に消失した。
  3. 最新モダントレンド(グラスモーフィズム)の即座な反映
    UIデザインにおいて「最近流行りのグラスモーフィズムにしたい」と伝えると、インラインコードで散らばっていたCSS設定を自動で style.css へリファクタリングし、背景にグラデーションと動く光の玉(Blob)、フォームには backdrop-filter を用いた「すりガラス効果」などのモダンなCSSを即座に適用した。
  4. 自律的なブラウザ操作による品質検証
    Antigravityは単にコードを書くだけでなく、バックグラウンドの「ブラウザサブエージェント」を用いて、実際に立ち上がった http://localhost:8080 へ自らアクセスする。「ログインAPIが正常に動くか」「画面が正しいスタイルになっているか」をスクリーンショットやDOM解析で自動検証し、その結果をもとにさらにコードを修正するという究極の自律型開発を実現した。

7. AI時代の関数型ドメイン駆動設計(Functional DDD)への道

これからClojure/ClojureScriptを利用するにあたりドメイン駆動設計について考えてみた。ドメイン駆動設計はEric Evansが提唱したソフトウェア設計手法であり、一定の開発者から熱狂的な支持を得ている。そしてこのドメイン駆動設計はオブジェクト指向の文脈で語られることが多いが、実は関数型プログラミングとも相性が良いことが、Scott Wlaschinの著書「Domain Modeling Made Functional」により提唱されている。
まず、DDDの概要を説明し、その上でオブジェクト指向と関数型プログラミングのどちらがDDDに適しているかを考察する。そして、Clojure/ClojureScriptにおけるDDDの実装方法について解説する。


1. DDDの2つの柱:戦略的設計と戦術的設計

DDDは「ビジネスの整理(戦略)」と「コードの実装(戦術)」の両輪で成り立つ。

① 戦略的設計 (Strategic Design) - 「境界」の画定

  • ユビキタス言語: 業務エキスパートと開発者が共有する厳密な共通言語。
  • 境界づけられたコンテキスト: 言語やルールの有効範囲を物理的(名前空間単位)に隔離し、モデルの汚染を防ぐ。
  • コンテキストマップ: 境界同士の連携(イベント駆動など)の全体図。
  • AIとの関わり: AIに「システムの役割(System Role)」や「ドメインの境界」を定義し、推論のスコープを限定させる。

② 戦術的設計 (Tactical Design) - 「核」の実装

  • 値オブジェクト / エンティティ: 全てを「不変なデータ(マップ)」として扱う。
  • 集約 (Aggregate): データの不変条件を保証する「純粋関数」。
  • リポジトリ: 副作用(永続化)を抽象化し、ドメインの「核」から分離する。
  • AIとの関わり: AIに「純粋な論理(データの変換)」を生成させる。

2. オブジェクト指向(OO)とAIの不適合性

AI(LLM)の推論モデルとOOの構造には、根本的なミスマッチが存在する。

  • 隠蔽された状態: クラス内部の状態(this.state)はAIにとって不可視のコンテキストとなり、副作用の予測を困難にさせ、ハルシネーションを誘発する。
  • 時間の依存性: 命令型コードは実行順序や過去の履歴に依存するため、AIはその「推論」にリソースを割かれ、論理ミスを犯しやすくなる。
  • 冗長なボイラープレート: Java等の型合わせやクラス定義のためのコードは、LLMのコンテキストウィンドウを浪費し、本質的なロジック生成を妨げる。

3. 言語特性比較:なぜ AI 時代のアーキテクチャは Clojure なのか

比較対象 パラダイム AI / 実務上の課題 Clojure (データ指向) の優位性
Python 命令型・OO 副作用が多く、AIがコード生成時に予期せぬバグを埋め込みやすい。 純粋関数が標準。入出力が透明なため、AIの推論と100%一致する。
Java 重厚なOO 複雑なクラス階層。AIが「型を合わせるためのコード」生成に疲弊する。 ボイラープレートの消滅。データを「ただのマップ」として扱う極簡設計。
Haskell 純粋関数型 高度な数学的概念(モナド等)により、動的なデータ(JSON等)の扱いが硬直的。 実用的な関数型malli 等で「必要な時だけ」検証する柔軟性。

4. 関数型DDDの実装パラダイム

Clojureのようなデータ指向言語では、DDDは以下のように洗練される。

  1. 「状態」の追放: 状態の変化を「古いデータ → 純粋関数 → 新しいデータ」の変換パイプラインにする。
  2. 不正な状態を表現不可能にする: malli スキーマを用い、論理的にありえないデータ構造を生成不能にする(Make Illegal States Unrepresentable)。
  3. アーキテクチャ (Functional Core, Imperative Shell):
    • 核 (Functional Core): 純粋関数のみ。AIが最も正確に生成・テスト可能な「論理の領域」。
    • 殻 (Imperative Shell): DB保存やAPI通信等の副作用を担当。最外周に隔離する。

5. AI駆動開発(SDD)と構造化プロンプト

AIに対する指示は、自然言語の曖昧さを排し、構造化された「形式言語的アプローチ」をとることで精度が最大化される。

  • アテンションの制御: XMLタグやS式(Clojureの構造)を用いることで、AIの注意を特定の制約やスキーマに強制的に束縛する。
  • S式の利点: コード自体がデータ構造であるため、AIにとって構造解析が容易であり、プロンプトとの相性が抜群。

6. ポスト・ラングチェーンとしてのClojureエコシステム

重厚なOOフレームワーク(LangChain等)を避け、透明性の高いツールでAIを制御する。

  • Agent-o-rama / Bosquet: プロンプト、ツール呼び出し、メモリの全てを「ただのデータ(EDN)」として扱う。
  • malli: データの構造化とバリデーションを担い、AIへの「厳密な指示書(Interface)」として機能。
  • REPL: AIが生成した断片的な関数を、稼働中のシステム内で即座に評価・デバッグできる唯一の環境。

7. アーキテクチャ制約

さらに以下のような制約を設けることにより、AIが生成するコードのアーキテクチャ品質を担保することを目指した。

# 標準アーキテクチャ定義ドキュメント (Clojure / ClojureScript)

本プロジェクトにおける、Webアプリケーション開発の標準アーキテクチャおよび技術スタックを以下に定義します。以降の開発(機能追加やコンポーネント作成)は、このドキュメントの規約に従って行われます。

## 1. 全体構成・ビルドツール
- **ビルド・開発サーバー**: `shadow-cljs`
- アプリケーション全体はフルスタック(Clojure/ClojureScript)で構成され、`shadow-cljs.edn``:dev-http` 機能を用いて、静的ファイルの配信とAPIサーバー(Ringハンドラ)を同一ポートにて動作させます。

## 2. フロントエンド (ClojureScript)
- **UIライブラリ**: `reagent` (Reactラッパー)
- **状態管理**: `reagent.core/atom` による単一・またはページごとの状態管理。
 - 基本的に `(defonce state (r/atom {...}))` のようにグローバルな状態を定義し、各コンポーネントから参照・更新(`swap!`, `reset!`)します。
- **通信(Ajax)**: `cljs-ajax`
 - バックエンドAPIとの通信には `ajax.core` (`GET`, `POST` など) を使用し、JSON形式でのリクエスト・レスポンスのやり取りを標準とします。
- **ルーティング(簡易的なSPA構築時)**:
 - `state` 内に `:current-page` のようなキーを持たせ、`case` 文などで描画するコンポーネントを切り替える方式を基本とします。規模が大きくなった場合は `reitit` などの導入を検討します。
- **スタイリング**: Vanilla CSS (`public/css/style.css` など)。

## 3. バックエンド (Clojure)
- **Webサーバー・ルーティング**: `ring` + `compojure`
 - APIのエンドポイントは `compojure.core/defroutes` で定義します。
 - `ring.middleware.json` (`wrap-json-response`, `wrap-json-body`) をミドルウェアに組み込み、JSONの自動パースとレスポンス処理を行います。
- **データベース・永続化**: `SQLite` + `next.jdbc`
 - 簡易かつポータブルなRDBとして SQLite (`org.xerial/sqlite-jdbc`) を採用します。
 - DBアクセスには標準的な `next.jdbc` および `next.jdbc.sql` を使用します。

## 4. ディレクトリ・ファイル構成
- `/shadow-cljs.edn`: ビルド・依存関係・開発サーバーの設定 (`:handler` にバックエンドの起点となるRingハンドラを指定)。
- `/src/app/core.cljs`: フロントエンドの起点、各種UIコンポーネント。
- `/src/backend/core.clj`: バックエンドの起点、RingハンドラおよびDBアクセス処理。
- `/public/`: `index.html``css/style.css`、コンパイル済みの `js/main.js` などを配置。

## 5. 新規機能追加のフロー
1. **バックエンド**: 
  - `src/backend/core.clj` に必要なAPIエンドポイント(Ring/Compojure route)を追加する。
  - データベースのテーブル変更やデータ操作処理を `next.jdbc` で記述する。
2. **フロントエンド通信**: 
  - `src/app/core.cljs``cljs-ajax` を用いて、作成したエンドポイントを呼び出す関数を追加する。
3. **フロントエンドUI**: 
  - Reagentコンポーネントを作成し、状態(`atom`)と通信処理を紐付ける。

結論

**「戦略的設計による境界の画定」を行い、その内部を「不変データと純粋関数」**で実装する。
このアプローチこそが、AIの推論能力を最も安全に引き出し、大規模で複雑なドメインを堅牢に維持するための、AI時代のソフトウェア工学における正解である。

まとめ

ClojureScript (shadow-cljs) と Clojure (Ring/Compojure) によるフルスタック環境を構築した。

  • Reagentatom による極めてシンプルで宣言的なUI
  • バックエンドのAPIとフロントエンドのシームレスな統合(アーキテクチャの統一)
  • Hot Reload と Calva (REPL接続) による、エディタ起点での高速なフィードバックループ

Reactベースのモダンなフロントエンドエコシステムにおいて、ClojureScriptを用いた関数型のアプローチとREPL駆動開発は、開発体験を劇的に向上させる強力な選択肢となる。

DDDと関数型DDDの推奨学習資料まとめ

① 全体像とエッセンス

② 実践と組織

  • DDD 指向マイクロサービスの設計: Microsoft公式の実践ガイド。
  • 増田 亨氏の公開スライド (Speaker Deck): 日本の現場に即したモデリングの基本。
  • 松崎 剛氏(little hands' lab)の Zenn: 具体的な実装ノウハウとよくある勘違いの解説。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?