用Angular构建一个真正的CRUD应用

关于如何开发CRUD应用的文章已然不计其数,我有意再添一篇,试图将重点落在“真正”两字上。 我发现网上很多示例通常会把开发CRUD应用说得很容易,而我想说这种程序的开发其实比大多数人想象的要难。我更不觉得开发CRUD很低端,相反我觉得是那些失真的示例,通过有意无意地隐藏复杂度,向世人传递了一种不实的映像。

我将使用一个常用的系统模块作为示例,即用户管理。“用户”是指被授予访问系统资源的人,我们需要一个UI针对用户对象进行增删改查的操作。这个示例能让我们真正看清一个CRUD应用从设计到开发的整个过程。您同时也能了解一些相关的思考、模式以及困难。

我选择Angular作为UI技术,因为我喜欢它的双向绑定和组件设计,这和我一直以来的开发理念是相符的。当然,开发CRUD应用还涉及许多其他技术,有些太重要了,以至于我们忘记了它们的存在。撇开操作系统和数据库不表,我选择在NodeJS上构建我的应用。您可以先看一下演示,再考虑是否值得花30分钟来阅读我这篇絮絮叨叨的博客。您还可以直接从Github上查阅完整的代码。

先建模

第一个问题,先从哪里入手? 有些人喜欢先绘制UI样例,而另外一些人则选择先设计数据库表。 无论采用哪种方法,您实际上都在进行建模思考。这个阶段,您唯一要考虑的是“用户”应该具有哪些属性。我这里选择用时下流行的JSON去建模。

{
   "r_user": {
   "USER_ID": "DH001", "USER_NAME": "VINCEZK", "PASSWORD": "Dark1234", "PWD_STATE": 1, "LOCK": null,
             "DISPLAY_NAME": "Vincent Zhang", "FAMILY_NAME": "Zhang", "GIVEN_NAME": "Vincent", "MIDDLE_NAME": null},
"r_employee": {
   "USER_ID": "DH001", "COMPANY_ID": "Darkhouse", "DEPARTMENT_ID": "Development", "TITLE": "Developer", "GENDER": "Male"},
"r_email": [
    {
   "EMAIL": "xxxx@hotmail.com", "TYPE": "private", "PRIMARY": 1},
    {
   "EMAIL": "xxxx@darkhouse.com", "TYPE": "work", "PRIMARY": 0}
],
"r_address": [
    {
   "ADDRESS_ID": 527, "COUNTRY": "China", "CITY": "Shanghai", "POSTCODE": 201202, 
     "ADDRESS_VALUE": "Room #999, Building #99, XXX Road #999", "TYPE": "Current Live", "PRIMARY": 1 },
    {
   "ADDRESS_ID": 528, "COUNTRY": "China", "CITY": "Haimen", "POSTCODE": 226126,
     "ADDRESS_VALUE": "Village LeeZhoo", "TYPE": "Born Place", "PRIMARY": 0}
],
"r_personalization": {
   "USER_ID": "DH001", "DATE_FORMAT": null, "DECIMAL_FORMAT": null, "TIMEZONE": "UTC+8", "LANGUAGE": "ZH" },
"relationshipWithRole": [
    {
   "NAME": "administrator"},
    {
   "NAME": "tester"}
]
}

JSON建模的好处是直接方便。您需要的只是一个文本编辑器。自然,你还得有一个模式规划。如上示例中,“ r_user”是将一些相关属性归在一起,我将之称为“relation”。如果某个“relation”具有多个元组,则用数组表示,例如“r_email”和“r_address”。如果实体与实体间存在某种关系,则参考“relationshipWithRole”。

以此JSON模型为基础,我们可以选择向上或向下扩展。向上通UI,向下连数据库。
到底先做哪个呢? 如果您是收钱办事,那么应先做UI部分,以便能尽早核实需求。就我而言,我没有这种压力,因此我会首先处理数据库,这样会节约一些开发时间。

接下来,我们将会面对一个老问题:对象关系映射。我目前的JSON模型是面向对象的,是否应使用相同的对象模式去存储它呢? 我的回答永远是“否”。我们存储数据的最终目的是方便以后查阅分析。而那个时候,我们将主要以“集合”的形式,而非“对象”形式去访问它们。如果我为了开始的便利,以对象形式存储,那么我将在查阅分析时付出更多的代价。所以我的选择一定是关系型数据库,并使用
JSON-On-Relations(简称JOR) 作为对象关系映射框架。

由于“用户”会对应到某个“人”,因此我创建了两个角色“system_user”和“employee”,并将它们分配给个实体“person”。
Entity: Person
在角色“system_user”中,我分配了4个relation,分别是:“r_address”,“r_email”,“r_personalization”,以及“r_user”。每个relation都有对应的基数设置。例如:“r_user”的“[1…1]”表示每个实例在“r_user”中必须具有1个数据项。
Role: system_user
使用JOR的图形建模工具,我可以轻松地创建数据库表并将它们组成“用户”实体。如您所见,它遵循实体关系模型这个听起来有点过时的概念,但实际上,它远比那些试图隐藏数据库的ORM深刻。此外,JOR提供了开箱即用的RESTful API,以满足对实体主要的CRUD操作。

然而就数据建模本身而言,绝非易事。 工具只能帮助您实现它,而无法帮助您设计它。困难点在于如何设计一个兼具可适用性、可扩展性和可重用性的数据模型。有人倡导“渐进式架构(Growing Architecture)”,认为架构一开始可以不用那么好,慢慢会变好。我认为这个理念并不适用于数据建模。精心设计的数据模型对于软件的生命周期至关重要;而粗制滥造的数据模型是不会渐进的。

在我设计“用户”模型时,我开始认为用户必须是一个人。然而这并不确切。 在A2A集成方案中使用的通信用户就不是一个自然人。它只是一种具有访问某些系统资源的凭据。因此,我将“system_user”定义为角色,而不是实体。当将其分配给某个自然人时,该人就具有系统用户这个角色,并以此能访问某些系统资源。如您所见,建模实际上是哲学性很强的思考,特别形而上学。

绘制UI

从架构上看,CRUD应用通常具有3层:数据库、应用服务器和用户界面(UI)。业务逻辑散布在这三层中,您很难消除其中任何一层。应用服务器处于数据库和UI之间,用于处理从UI发来的请求,并将之转换为对数据库的访问请求。没有这个中间层,将会极大加重UI与数据库的通讯成本。与单用户应用相比(例如Office Word),CRUD应用是必须要支持多用户并发访问的

接下来,我将绘制用户界面。取决于您对UI技术的熟悉程度,您既可以用铅笔和纸来绘制UI,也可以利用一些UI建模工具,或直接使用正式的UI开发工具。由于我在数据建模的同时想象了UI,因此我就直接用HTML和CSS来绘制UI,这样我可以节省很多时间。 我的UI共有2个页面:“搜索和列表”页面,以及“详细”页面。
Search&List Page
“搜索和列表”页面除了允许您搜索和列示用户外,您还可以创建一个新用户,或删除一个现有用户。单击User ID链接,或者点击显示/更改操作按钮,将导航到“详细”页面。
Detail Page
“详细”页面显示用户的详细信息。它具有一个固定的抬头,以及5个自由选项卡,用于对信息进行分组。右上角有“编辑/显示”和“保存”按钮。

这两个页面目前还是静态的。 数据是固定的,按钮是禁用的,链接是假的。我只是想先尽快把它画出来,看看是否符合我的预期。在实际项目中,您可能需要将这样的页面给产品经理过目,以检查是否满足需求。

也许有人会问:“为什么你还要人工绘制UI?不是有很多工具可以根据数据模型自动生成UI吗?”我的回答是:就我所知的产品和经历过的项目,我从未见过这种方式真正成功过。也许,许多技术布道师正在宣讲这样的开发方式,例如低代码或无代码,但我不相信这个能成功。

正如我前面所说的三层架构,您无法消除其中的任何一层。每个层都有自己的建模语言来描述相同的实体对象。数据库使用关系代数语言以实现对物理存储的全路径访问。应用服务器层使用面向对象的语言来操作内存中的数据。UI尝试用便于人类理解的语言以使其更加用户友好。由于每个层各有其不同的侧重,我们几乎无法用一个固定的规则使得其他层能基于某层自动产生。我们可以做的是翻译和映射这3种不同层面的建模语言。从数据库到应用服务器,我们使用对象关系映射;从应用服务器到UI,我们使用UI对象映射。

对象到UI的映射

实际上,我还是比较喜欢绘制UI的。尤其是当你有趁手的工具能让你实时观察调整效果。在这里,我使用Bootstrap进行排版,用Angular Server进行实时渲染。当UI看起来不错后,就该做数据绑定和界面逻辑了。使用Angular的Reactive Form让这件事情变得很简单。

无论UI的外观如何,其幕后都对应一个数据对象。这里所说的“数据对象”可以用一种嵌套结构表示。比如抬头下面包含行项目,行项目下面再包含子项目。我的“用户对象”用Angular的FormGroup可表示如下:

this.userForm = this.fb.group({
   
  USER_ID: ['DH001', [Validators.required]], LOCK: ['Unlocked'], PWD_STATUS: [''],
  userBasic: this.fb.group({
   
    names: this.fb.group({
   
      USER_NAME: ['VINCEZK', [Validators.required]],
      DISPLAY_NAME: ['Vincent Zhang', [Validators.required]],
      GIVEN_NAME: ['Vincent'], MIDDLE_NAME: [''], FAMILY_NAME: ['Zhang']
    }),
    employee: this.fb.group({
   
      TITLE: ['Developer'], DEPARTMENT_ID: ['Development'], 
      COMPANY_ID: ['Darkhouse', [Validators.required]], GENDER: ['Male']
    })
  }),
  emails:  this.fb.array([
    this.fb.group({
   
      EMAIL: ['DH001@hotmail.com'], TYPE: ['private'], PRIMARY: ['1']
    });
    this.fb.group({
   
      EMAIL: ['DH001@darkhouse.com'], TYPE: ['work'], PRIMARY: ['0']
    });
  ]),
  addresses: this.fb.array([
    this.fb.group({
   
      ADDRESS_ID: [''], TYPE: ['Current Live', [Validators.required]],
      ADDRESS_VALUE: ['Room #999, Building #99, XXX Road #999', [Validators.required]],
      POSTCODE: ['201202'], CITY: ['Shanghai'], COUNTRY: ['China'], PRIMARY: ['1']
    })
  ]),
  userPersonalization: this.fb.group({
   
    USER_ID: ['DH001'], LANGUAGE: ['ZH'], TIMEZONE: ['UTC+8'], DECIMAL_FORMAT: [''], DATE_FORMAT: ['']
  }),
  userRole: this.fb.array([
    this.fb.group({
   
      NAME: ['administrator'], DESCRIPTION: ['Administrator'],
      system_role_INSTANCE_GUID: ['391E75B02A1811E981F3C33C6FB0A7C1'],
      RELATIONSHIP_INSTANCE_GUID: ['06FEB4702A1B11E981F3C33C6FB0A7C1']
    })
  ])
}); 

Angular引入了FormGroup及其构建器(this.fb)来构建UI数据对象。它不仅可以用来定义对象结构和数值,还可以添加验证器(Validator)。例如,我在属性“USER_ID”上添加了“Validators.required”,以声明它不允许为空。此外,FormGroup还实现了UI(HTML)和对象(JS)之间的双向数值绑定。这意味着在UI上进行的任何数据更改可实时同步到背后的数据对象上,反之亦然。

<div class="col-lg-4 form-group" 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AngularJS是一个基于MVC模式的JavaScript前端框架,它能够帮助开发者快速而且高效地构建复杂的单页面Web应用。下面是一个使用AngularJS实现增删查改界面的简单示例: 1. 首先,定义一个AngularJS模块,并注入ngRoute和ngResource模块: ``` var app = angular.module('myApp', ['ngRoute', 'ngResource']); ``` 2. 定义一个路由,指定每个视图对应的控制器和模板: ``` app.config(function($routeProvider) { $routeProvider .when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }) .when('/list', { templateUrl: 'partials/list.html', controller: 'ListController' }) .when('/add', { templateUrl: 'partials/add.html', controller: 'AddController' }) .when('/edit/:id', { templateUrl: 'partials/edit.html', controller: 'EditController' }) .otherwise({ redirectTo: '/' }); }); ``` 3. 定义一个服务,用于与后端API进行数据交互: ``` app.factory('Item', function($resource) { return $resource('/api/items/:id'); }); ``` 4. 定义一个控制器,用于处理首页的逻辑: ``` app.controller('HomeController', function($scope) { // 首页逻辑 }); ``` 5. 定义一个控制器,用于处理列表页的逻辑: ``` app.controller('ListController', function($scope, Item) { $scope.items = Item.query(); }); ``` 6. 定义一个控制器,用于处理添加页的逻辑: ``` app.controller('AddController', function($scope, $location, Item) { $scope.item = new Item(); $scope.save = function() { $scope.item.$save(function() { $location.path('/list'); }); }; }); ``` 7. 定义一个控制器,用于处理编辑页的逻辑: ``` app.controller('EditController', function($scope, $routeParams, $location, Item) { $scope.item = Item.get({ id: $routeParams.id }); $scope.save = function() { $scope.item.$save(function() { $location.path('/list'); }); }; }); ``` 8. 最后,在HTML中引入AngularJS和相关的脚本和样式文件,并使用ng-view指令指定视图的位置: ``` <!DOCTYPE html> <html ng-app="myApp"> <head> <meta charset="utf-8"> <title>AngularJS CRUD</title> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular-route.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular-resource.min.js"></script> <script src="app.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">AngularJS CRUD</a> </div> <ul class="nav navbar-nav"> <li><a href="#/">Home</a></li> <li><a href="#/list">List</a></li> <li><a href="#/add">Add</a></li> </ul> </div> </nav> <div class="container"> <div ng-view></div> </div> </body> </html> ``` 以上就是一个使用AngularJS实现增删查改界面的简单示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值