これらの状況はそれほど変わらないので、最初に単純なケースを見てみましょう。
- ある種のモデルがあります:
module model; import std.math; struct Point { float x, y; } float sqr(float v) { return v * v; } float dist()(auto ref const(Point) a, auto ref const(Point) b) { return sqrt(sqr(ax - bx) + sqr(ay - by)); } class Model { float triangleAreaByLengths(float a, float b, float c) { auto p = (a + b + c) / 2; return sqrt(p * (p - a) * (p - b) * (p - c)); } float triangleAreaByPoints(Point a, Point b, Point c) { auto ab = dist(a, b); auto ac = dist(a, c); auto bc = dist(b, c); return triangleAreaByLengths(ab, ac, bc); } }
- それを使用するコードがあります:
import std.stdio; import model; void main() { auto a = Point(1, 2); auto b = Point(3, 4); auto c = Point(4, 1); auto m = new Model; writeln(m.triangleAreaByPoints(a, b, c)); }
したがって、2を作成するために何をする必要があります-1つの通常のアプリケーションからの休息サーバーとシンクライアント:
- モデルインターフェイスを強調表示します。
- サーバーコードを作成します。
- 実際のモデルの代わりに、残りの実装を作成します。
退屈だが重要なポイント
まず、モデルについて少し。 執筆時点では、vibe-d-0.7.30-beta.1は関数オーバーロード(一般的に)をサポートしていませんでした。これは、部分的に論理的です。ネットワークに関する引数を渡すため、引数に関する正確な情報なしでメソッドを呼び出そうとするためですどのタイプに導かれるべきかさえ知りません-徹底的な検索で見つける必要がありますが、微妙なポイントがあります(たとえば、「5」はintとfloatの両方に持ち込むことができます)。
さらに、メソッドの引数と戻りデータは、vibe.data.jsonを使用して直列化[de]できる必要があります。 すべての組み込みデータ型と単純な構造(プライベートフィールドなし)でこれを行うことができます。 [de]シリアル化を実装するには、2つのメソッド
および
を宣言できます。これは、複雑な構造をJson型に変換するプロセスを記述します。
これは返されたインターフェイスには適用されません。ネットワーク経由で引数を渡すことでも機能しますが、別の点があります。返されたインターフェイスオブジェクトを実装するクラスのインスタンスを返すメソッドは引数を受け入れてはなりません。 ここで1つだけ説明できます:インスタンスは残りのインターフェイスを登録するために使用され、関数が引数を受け入れる場合、init値を持つ引数を持つインスタンスを作成することはおそらく不可能ですが、何らかの方法でネストされたインターフェイスを登録するためにインスタンスを作成する必要があります。
さらに、メソッドの引数と戻りデータは、vibe.data.jsonを使用して直列化[de]できる必要があります。 すべての組み込みデータ型と単純な構造(プライベートフィールドなし)でこれを行うことができます。 [de]シリアル化を実装するには、2つのメソッド
static MyType fromJson(Json data)
および
Json toJson() const
を宣言できます。これは、複雑な構造をJson型に変換するプロセスを記述します。
これは返されたインターフェイスには適用されません。ネットワーク経由で引数を渡すことでも機能しますが、別の点があります。
そのため、インターフェースを選択します。
interface IModel { @method(HTTPMethod.GET) float triangleAreaByLengths(float a, float b, float c); @method(HTTPMethod.GET) float triangleAreaByPoints(Point a, Point b, Point c); } class Model : IModel { ... }
@method(HTTPMethod.GET)
デコレータ
@method(HTTPMethod.GET)
、ルーティングの構築に必要です。 それらなしで行う方法もあります-メソッドの命名規則(プレフィックス)を使用します:
-
get
、query
-GET
メソッド。 -
set
、put
-PUT
; -
add
、create
、post
POST
; -
remove
、erase
、delete
DELETE
; -
update
、patch
PATCH
。
サーバーコードは、モジュールの静的コンストラクターのクラシックな雰囲気で記述されます。
shared static this() { auto router = new URLRouter; router.registerRestInterface(new Model); // auto set = new HTTPServerSettings; set.port = 8080; set.bindAddresses = ["127.0.0.1"]; listenHTTP(set, router); }
そして最後に、モデルを使用したコードの変更:
... auto m = new RestInterfaceClient!IModel("http://127.0.0.1:8080/"); // ...
フレームワーク自体は、サーバーへの呼び出しとデータ型の[de]シリアル化を実装します。
その結果、アプリケーションをサーバーとクライアントに分割し、既存のコードを最小限に変更しました! ところで、スローされた例外は、残念なことに、例外のタイプを保存せずに 、バイブによってクライアントアプリケーションにスローされます。
より複雑なケースを考えてみましょう-モデルには、シリアル化できないオブジェクト(クラス)の配列を返すメソッドがあります。 残念ながら、既存のコードを変更せずにはできません。 この例では、この状況を認識しています。
異なるポイントアグリゲーターを返します。
interface IPointCalculator { struct CollectionIndices { string _name; } // @method(HTTPMethod.GET) Point calc(string _name, Point[] points...); } interface IModel { ... @method(HTTPMethod.GET) Collection!IPointCalculator calculator(); } class PointCalculator : IPointCalculator { Point calc(string _name, Point[] points...) { import std.algorithm; if (_name == "center") { auto n = points.length; float cx = points.map!"ax".sum / n; float cy = points.map!"ay".sum / n; return Point(cx, cy); } else if (_name == "left") return points.fold!((a,b)=>ax<bx?a:b); else throw new Exception("Unknown calculator '" ~ _name ~ "'"); } } class Model : IModel { PointCalculator m_pcalc; this() { m_pcalc = new PointCalculator; } ... Collection!IPointCalculator calculator() { return Collection!IPointCalculator(m_pcalc); } }
本質的に、
IPointCalculator
はコレクションの要素ではありませんが、コレクション自体と
CollectionIndices
構造は、このコレクションの要素を取得するために使用されるインデックスの存在を示すだけです。
_name
前の下線は、
calc
メソッドへの要求の形式を
calculator/:name/calc
として決定します。ここで、
:name
メソッドの最初のパラメーターとして渡され、
CollectionIndices
では、
new RestInterfaceClient!IModel
を使用してインターフェイスを実装するときにこのような要求を作成できます。
次のように使用されます。
... writeln(m.calculator["center"].calc(a, b, c)); ...
戻り値の型が
Collection!IPointCalculator
から
IPointCalculator
変更された場合、ほとんど変更されません。
... writeln(m.calculator.calc("center", a, b, c)); ...
この場合、リクエストの形式は同じままです。 この組み合わせにおける
Collection
の役割は完全には明らかではありません。
前菜には、クライアントのWebバージョンを実装します。 これを行うには、次のものが必要です。
- レストAPIを使用して、jsコードでhtmlページを作成します。
- サーバー側に小さなコードを追加します。
vibeで使用されるダイエットテンプレートは、 jadeに非常に似ています :
html head title REST style. .label { display: inline-block; width: 20px; } input { width: 100px; } script(src = "model.js") script. function getPoints() { var ax = parseFloat(document.getElementById('ax').value); var ay = parseFloat(document.getElementById('ay').value); var bx = parseFloat(document.getElementById('bx').value); var by = parseFloat(document.getElementById('by').value); var cx = parseFloat(document.getElementById('cx').value); var cy = parseFloat(document.getElementById('cy').value); return [{x:ax, y:ay}, {x:bx, y:by}, {x:cx, y:cy}]; } function calcTriangleArea() { var p = getPoints(); IModel.triangleAreaByPoints(p[0], p[1], p[2], function(r) { document.getElementById('area').innerHTML = r; }); } body h1 div div.label A: input#ax(placehoder="ax",value="1") input#ay(placehoder="ay",value="2") div div.label B: input#bx(placehoder="bx",value="2") input#by(placehoder="by",value="1") div div.label C: input#cx(placehoder="cx",value="0") input#cy(placehoder="cy",value="0") div button(onclick="calcTriangleArea()") p : span#area
もちろん、まあまあですが、規範の例としては:
サーバーコードの変更:
... auto restset = new RestInterfaceSettings; restset.baseURL = URL("http://127.0.0.1:8080/"); router.get("/model.js", serveRestJSClient!IModel(restset)); router.get("/", staticTemplate!"index.dt"); ...
ご覧のとおり、vibeはAPIにアクセスするためのjsコードを生成します。
結論として、この段階では、たとえば、返されたすべてのインターフェイス(jsオブジェクトのこれらのフィールド)のjsコードの不正な生成、および特定のコレクション(url-
:name
不正な生成)交換済み)。 しかし、これらの粗さは簡単に修正できます 。近い将来修正されると思います 。
それだけです! サンプルコードはgithubからダウンロードできます。