迅速なアプリケーション開発フレームワークAllcountJSを使用したアプリケーション開発
公開: 2022-03-11Rapid Application Development(RAD)のアイデアは、従来のウォーターフォール開発モデルに対応して生まれました。 RADには多くのバリエーションがあります。 たとえば、アジャイル開発やRationalUnifiedProcessです。 ただし、このようなモデルにはすべて共通点が1つあります。それは、プロトタイピングと反復型開発を通じて、最小限の開発時間で最大のビジネス価値を生み出すことを目的としていることです。 これを実現するために、Rapid Application Developmentモデルは、プロセスを容易にするツールに依存しています。 この記事では、そのようなツールの1つと、それを使用してビジネス価値と開発プロセスの最適化に焦点を当てる方法について説明します。
AllcountJSは、迅速なアプリケーション開発を念頭に置いて構築された新しいオープンソースフレームワークです。 これは、アプリケーションの構造と動作を記述するJSONのような構成コードを使用した宣言型アプリケーション開発のアイデアに基づいています。 フレームワークはNode.js、Express、MongoDBの上に構築されており、AngularJSとTwitterBootstrapに大きく依存しています。 宣言型パターンに依存していますが、フレームワークでは、必要に応じてAPIに直接アクセスすることでさらにカスタマイズできます。
RADフレームワークとしてAllcountJSを使用する理由
ウィキペディアによると、迅速なアプリケーション開発を約束するツールは少なくとも100ありますが、これは疑問を投げかけます。それは、「迅速」であるということです。 これらのツールを使用すると、特定のデータ中心のアプリケーションを数時間で開発できますか? または、アプリケーションを数日または数週間で開発できる場合は、おそらく「迅速」です。 これらのツールの中には、動作するアプリケーションを作成するのに数分かかるとさえ主張しているものもあります。 ただし、5分以内に有用なアプリケーションを構築しても、すべてのビジネスニーズを満たしていると主張できる可能性はほとんどありません。 AllcountJSはそのようなツールであるとは主張していません。 AllcountJSが提供するのは、アイデアを短期間でプロトタイプ化する方法です。
AllcountJSフレームワークを使用すると、テーマに沿った自動生成されたユーザーインターフェイス、ユーザー管理機能、RESTful API、およびその他のいくつかの機能を備えたアプリケーションを最小限の労力と時間で構築できます。 AllcountJSはさまざまなユースケースで使用できますが、さまざまなビューを持つさまざまなオブジェクトのコレクションがあるアプリケーションに最適です。 通常、ビジネスアプリケーションはこのモデルに適しています。
AllcountJSは、allcountjs.comと、そのプロジェクトトラッカーの構築に使用されています。 allcountjs.comはカスタマイズされたAllcountJSアプリケーションであり、AllcountJSを使用すると、静的ビューと動的ビューの両方を簡単に組み合わせることができます。 動的にロードされたパーツを静的コンテンツに挿入することもできます。 たとえば、AllcountJSはデモアプリケーションテンプレートのコレクションを管理します。 allcountjs.comのメインページには、そのコレクションからランダムなアプリケーションテンプレートをロードするデモウィジェットがあります。 他のいくつかのサンプルアプリケーションは、allcountjs.comのギャラリーで入手できます。
入門
RADフレームワークAllcountJSの機能の一部を示すために、Toptal用の単純なアプリケーションを作成します。これをToptalCommunityと呼びます。 私たちのブログをフォローすると、以前のブログ投稿の一部としてHoodieを使用して同様のアプリケーションが構築されたことをすでにご存知かもしれません。 このアプリケーションを使用すると、コミュニティメンバーはサインアップしてイベントを作成し、それらへの参加を申し込むことができます。
環境を設定するには、Node.js、MongoDB、Gitをインストールする必要があります。 次に、「npm install」コマンドを呼び出して、AllcountJS CLIをインストールし、プロジェクトの初期化を実行します。
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLIは、package.jsonを事前入力するために、プロジェクトに関する情報を入力するように求めます。
AllcountJSは、スタンドアロンサーバーまたは依存関係として使用できます。 最初の例では、AllcountJSを拡張しないので、スタンドアロンサーバーが機能するはずです。
この新しく作成されたapp-configディレクトリ内で、main.jsJavaScriptファイルの内容を次のコードスニペットに置き換えます。
A.app({ appName: "Toptal Community", onlyAuthenticated: true, allowSignUp: true, appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }] }, UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } }, User: { views: { OnlyNameUser: { permissions: { read: null, write: ['admin'] } }, fields: { username: Fields.text("User name") } } } } } });
AllcountJSはGitリポジトリで動作しますが、簡単にするために、このチュートリアルでは使用しません。 Toptal Communityアプリケーションを実行するには、toptal-community-allcountディレクトリでAllcountJSCLIrunコマンドを呼び出すだけです。
allcountjs run
このコマンドを実行するときは、MongoDBが実行されている必要があることに注意してください。 すべてがうまくいけば、アプリケーションはhttp:// localhost:9080で稼働しているはずです。
ログインするには、ユーザー名「admin」とパスワード「admin」を使用してください。
100行未満
main.jsで定義されたアプリケーションが91行のコードしか必要としないことに気づいたかもしれません。 これらの行には、http:// localhost:9080に移動したときに観察される可能性のあるすべての動作の宣言が含まれています。 では、内部で何が起こっているのでしょうか。 アプリケーションの各側面を詳しく見て、コードがそれらにどのように関連しているかを見てみましょう。
サインイン&サインアップ
アプリケーションを開いた後に表示される最初のページはサインインです。これは、フォームを送信する前に「サインアップ」というラベルの付いたチェックボックスがオンになっていると仮定すると、サインアップページとしても機能します。
このページが表示されるのは、main.jsファイルで、認証されたユーザーのみがこのアプリケーションを使用できると宣言されているためです。 さらに、ユーザーがこのページからサインアップできるようにします。 これに必要なのは、次の2行だけです。
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
ウェルカムページ
サインインすると、アプリケーションメニューのあるウェルカムページにリダイレクトされます。 アプリケーションのこの部分は、「menuItems」キーで定義されたメニュー項目に基づいて自動的に生成されます。
他のいくつかの関連する構成とともに、メニューはmain.jsファイルで次のように定義されています。
A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });
AllcountJSはFontAwesomeアイコンを使用するため、構成で参照されるすべてのアイコン名はFontAwesomeアイコン名にマップされます。
イベントの閲覧と編集
メニューから[イベント]をクリックすると、下のスクリーンショットに示す[イベント]ビューが表示されます。 これは、対応するエンティティにいくつかの一般的なCRUD機能を提供する標準のAllcountJSビューです。 ここでは、イベントを検索したり、新しいイベントを作成したり、既存のイベントを編集または削除したりできます。 このCRUDインターフェースには、リストとフォームの2つのモードがあります。 アプリケーションのこの部分は、次の数行のJavaScriptコードを使用して構成されます。
A.app({ ..., entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], ... } } } });
この例は、AllcountJSでエンティティの説明がどのように構成されているかを示しています。 関数を使用してエンティティを定義していることに注目してください。 AllcountJS構成のすべてのプロパティを関数にすることができます。 これらの関数は、引数名を介して依存関係を解決するように要求できます。 関数が呼び出される前に、適切な依存関係が挿入されます。 ここで、「フィールド」は、エンティティフィールドを記述するために使用されるAllcountJS構成APIの1つです。 プロパティ「エンティティ」には、名前と値のペアが含まれます。名前はエンティティタイプの識別子であり、値はその説明です。 この例では、イベントのエンティティタイプが記述されており、タイトルは「イベント」です。 default-sort-ordering、参照名などの他の構成もここで定義できます。 Default-sort-orderはフィールド名と方向の配列で定義され、参照名は文字列で定義されます(詳細はこちらをご覧ください)。
この特定のエンティティタイプは、「eventName」、「date」、「time」、「appliedUsers」の4つのフィールドを持つものとして定義されており、最初の3つはデータベースに保持されます。 「required()」の使用によって示されるように、これらのフィールドは必須です。 以下のスクリーンショットに示すように、このようなルールを持つこれらのフィールドの値は、フォームがフロントエンドで送信される前に検証されます。 AllcountJSは、クライアント側とサーバー側の両方の検証を組み合わせて、最高のユーザーエクスペリエンスを提供します。 4番目のフィールドは、イベントへの参加を申し込んだユーザーのリストを含む関係です。 当然、このフィールドはデータベースに保持されず、イベントに関連するAppliedUserエンティティのみを選択することで入力されます。
イベントへの参加に申し込む
ユーザーが特定のイベントを選択すると、ツールバーに「適用」というラベルの付いたボタンが表示されます。 それをクリックすると、イベントがユーザーのスケジュールに追加されます。 AllcountJSでは、これと同様のアクションは、構成で宣言するだけで構成できます。
actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }]
任意のエンティティタイプのプロパティ「アクション」は、各カスタムアクションの動作を説明するオブジェクトの配列を取ります。 各オブジェクトには、アクションの一意の識別子を定義する「id」プロパティがあり、プロパティ「name」は表示名を定義し、プロパティ「actionTarget」はアクションコンテキストを定義するために使用されます。 「actionTarget」を「single-item」に設定すると、特定のイベントでアクションを実行する必要があることを示します。 プロパティ「perform」で定義された関数は、このアクションが実行されたとき、通常はユーザーが対応するボタンをクリックしたときに実行されるロジックです。

この関数によって依存関係が要求される場合があります。 たとえば、この例では、関数は「ユーザー」、「アクション」、「Crud」に依存しています。 アクションが発生すると、「ユーザー」依存関係を要求することにより、このアクションを呼び出すユーザーへの参照を取得できます。 これらのエンティティのデータベース状態の操作を可能にする「Crud」依存関係もここで要求されます。 Crudオブジェクトのインスタンスを返す2つのメソッドは次のとおりです。メソッド「actionContextCrud()」-アクション「Apply」が属するため「Event」エンティティタイプのCRUDを返し、メソッド「crudForEntityType()」-CRUDを返しますタイプIDで識別されるエンティティタイプの場合。
アクションの実装は、このイベントがユーザーに対して既にスケジュールされているかどうかを確認することから始まり、スケジュールされていない場合は作成します。 すでにスケジュールされている場合は、「Actions.modalResult()」の呼び出しから値を返すことでダイアログボックスが表示されます。 アクションは、モーダルを表示するだけでなく、「ビューに移動」、「ビューを更新」、「ダイアログを表示」など、さまざまなタイプの操作を同様の方法で実行できます。
適用されたイベントのユーザースケジュール
イベントに正常に適用されると、ブラウザは「マイイベント」ビューにリダイレクトされます。このビューには、ユーザーが適用したイベントのリストが表示されます。 ビューは、次の構成によって定義されます。
UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } },
この場合、新しい構成プロパティ「フィルタリング」を使用しています。 前の例と同様に、この関数も「ユーザー」依存関係に依存しています。 関数がオブジェクトを返す場合、それはMongoDBクエリとして扱われます。 クエリは、現在のユーザーのみに属するイベントのコレクションをフィルタリングします。
もう1つの興味深いプロパティは、「ビュー」です。 「ビュー」は通常のエンティティタイプですが、MongoDBコレクションは親エンティティタイプの場合と同じです。 これにより、データベース内の同じデータに対して視覚的に異なるビューを作成できます。 実際、この機能を使用して、「UserEvent」の2つの異なるビュー「MyEvent」と「AppliedUser」を作成しました。 サブビューのプロトタイプは親エンティティタイプに設定されているため、オーバーライドされないプロパティは親タイプから「継承」されます。
イベント参加者のリスト
イベントに申し込んだ後、他のユーザーは参加を計画しているすべてのユーザーのリストを見ることができます。 これは、main.jsの次の構成要素の結果として生成されます。
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")
「AppliedUser」は、「MyEvent」エンティティタイプの読み取り専用ビューです。 この読み取り専用のアクセス許可は、アクセス許可オブジェクトの「書き込み」プロパティに空の配列を設定することで適用されます。 また、「読み取り」権限が定義されていないため、デフォルトでは、すべてのユーザーに読み取りが許可されています。
デフォルト実装の拡張
RADフレームワークの典型的なバックドローは、柔軟性の欠如です。 アプリを作成してカスタマイズする必要があると、重大な障害に遭遇する可能性があります。 AllcountJSは拡張性を念頭に置いて開発されており、内部のすべてのビルディングブロックを置き換えることができます。
これを実現するために、AllcountJSは独自の依存性注入(DI)実装を使用します。 DIを使用すると、開発者は拡張ポイントを介してフレームワークのデフォルトの動作をオーバーライドできると同時に、既存の実装を再利用することができます。 RADフレームワーク拡張の多くの側面は、ドキュメントで説明されています。 このセクションでは、フレームワークの多くのコンポーネントのうち2つ、サーバー側のロジックとビューを拡張する方法について説明します。
Toptal Communityの例を続けて、外部データソースを統合してイベントデータを集約しましょう。 各イベントの前日にイベントの計画について議論しているToptalブログの投稿があると想像してみてください。 Node.jsを使用すると、ブログのRSSフィードを解析してそのようなデータを抽出できるはずです。 これを行うには、「request」、「xml2js」(Toptal Blog RSSフィードをロードするため)、「q」(promiseを実装するため)、「moment」(日付を解析するため)などの追加のnpm依存関係が必要になります。 これらの依存関係は、次の一連のコマンドを呼び出すことでインストールできます。
npm install xml2js npm install request npm install q npm install moment
別のJavaScriptファイルを作成し、toptal-community-allcountディレクトリに「toptal-community.js」という名前を付けて、次のように入力します。
var request = require('request'); var Q = require('q'); var xml2js = require('xml2js'); var moment = require('moment'); var injection = require('allcountjs'); injection.bindFactory('port', 9080); injection.bindFactory('dbUrl', 'mongodb://localhost:27017/toptal-community'); injection.bindFactory('gitRepoUrl', 'app-config'); injection.bindFactory('DiscussionEventsImport', function (Crud) { return { importEvents: function () { return Q.nfcall(request, "https://www.toptal.com/blog.rss").then(function (responseAndBody) { var body = responseAndBody[1]; return Q.nfcall(xml2js.parseString, body).then (function (feed) { var events = feed.rss.channel[0].item.map(function (item) { return { eventName: "Discussion of " + item.title, date: moment(item.pubDate, "DD MMM YYYY").add(1, 'day').toDate(), time: "12:00" }}); var crud = Crud.crudForEntityType('Event'); return Q.all(events.map(function (event) { return crud.find({query: {eventName: event.eventName}}).then(function (createdEvent) { if (!createdEvent[0]) { return crud.createEntity(event); } }); } )); }); }) } }; }); var server = injection.inject('allcountServerStartup'); server.startup(function (errors) { if (errors) { throw new Error(errors.join('\n')); } });
このファイルでは、「DiscussionEventsImport」と呼ばれる依存関係を定義しています。これは、「Event」エンティティタイプにインポートアクションを追加することで、main.jsファイルで使用できます。
{ id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
JavaScriptファイルにいくつかの変更を加えた後、サーバーを再起動することが重要であるため、前のインスタンスを強制終了し、前と同じコマンドを実行してサーバーを再起動できます。
node toptal-community.js
すべてがうまくいけば、「ブログイベントのインポート」アクションを実行した後、以下のスクリーンショットのようなものが表示されます。
これまでのところ良いですが、ここで止まらないようにしましょう。 デフォルトのビューは機能しますが、退屈な場合があります。 それらを少しカスタマイズしましょう。
あなたはカードが好きですか? みんなカードが好き! カードビューを作成するには、app-configディレクトリ内のevents.jadeという名前のファイルに以下を配置します。
extends main include mixins block vars - var hasToolbar = true block content .refresh-form-controller(ng-app='allcount', ng-controller='EntityViewController') +defaultToolbar() .container.screen-container(ng-cloak) +defaultList() .row: .col-lg-4.col-md-6.col-xs-12(ng-repeat="item in items") .panel.panel-default .panel-heading h3 {{item.date | date}} {{item.time}} div button.btn.btn-default.btn-xs(ng-if="!isInEditMode", lc-tooltip="View", ng-click="navigate(item.id)"): i.glyphicon.glyphicon-chevron-right | button.btn.btn-danger.btn-xs(ng-if="isInEditMode", lc-tooltip="Delete", ng-click="deleteEntity(item)"): i.glyphicon.glyphicon-trash .panel-body h3 {{item.eventName}} +noEntries() +defaultEditAndCreateForms() block js +entityJs()
その後、main.jsの「Event」エンティティから「customView:「events」」として参照するだけです。 アプリを実行すると、デフォルトの表形式のインターフェースではなく、カードベースのインターフェースが表示されます。
結論
今日、Webアプリケーションの開発フローは、多くのWebテクノロジー間で類似しており、一部の操作が何度も繰り返されます。 それは本当に価値がありますか? たぶん、Webアプリケーションの開発方法を再考する時が来たのでしょうか。
AllcountJSは、迅速なアプリケーション開発フレームワークへの代替アプローチを提供します。 まず、エンティティの説明を定義してアプリケーションのスケルトンを作成し、次にその周りにビューと動作のカスタマイズを追加します。 ご覧のとおり、AllcountJSを使用して、100行未満のコードで、シンプルでありながら完全に機能するアプリケーションを作成しました。 たぶん、それはすべての生産要件を満たしているわけではありませんが、カスタマイズ可能です。 これらすべてにより、AllcountJSはWebアプリケーションを迅速にブートストラップするための優れたツールになります。