破狼 Blog

Write less got more.

angularjs之browserTrigger

今天推荐一款来自angularjs源码的单元测试辅助库browserTrigger,这是来自于ngScenario的一段代码。主要用户触发浏览器型行为更新ng中scope view model的值。

这是angularjs源码中单元测试的使用browserTrigger的实例:

it('should set the model to empty string when empty option is selected', function() {
    scope.robot = 'x';
    compile('<select ng-model="robot">' +
              '<option value="">--select--</option>' +
              '<option value="x">robot x</option>' +
              '<option value="y">robot y</option>' +
            '</select>');
    expect(element).toEqualSelect('', ['x'], 'y');

    browserTrigger(element.find('option').eq(0));
    expect(element).toEqualSelect([''], 'x', 'y');
    expect(scope.robot).toBe('');
  });

在这段代码中给browserTrigger传入你希望选择的select option,则它会帮助你tigger change,选中当前option,更触发更新ng select的viewmodel。

在browserTrigger中还为我们做了很多其他输入框或者html控件的触发接口,同时也加入了浏览器的兼容性。使得我们的测试更加方便不用考虑浏览器兼容性或者不同的html控件trigger不同的事件去更新scope的值。

具体更多信息请参考ng的官方测试browserTrigger源码

软件架构设计模式简述

在软件开发设计中我们经常会面对业务分析,提取领域问题,从而实现软件架构设计。关于 软件架构设计Martin Fowler在2004出版的《企业应用架构模式》中 概括了四种方式的架构模式。它们分别为事务性脚本,表驱动模式,活动记录模式,领域驱动设计。前两者事务性脚本,表驱动模式作为 面向过程方式架构设计,后两者为面向对象架构设计。它们适合于不同的业务场景,它们也各有长短。

事务脚本模式

事务脚本模式是架构设计中最简单的架构模式,面向过程模式。该模式以用户的操作,UI表现为起点,设计业务组件, 即业务逻辑将直接映射到用户界面的操作。这通常是从表现层逻辑出发,表现层需要什么那么业务层就提供什么, 直到数据层。针对每一个用户的新功能都需要新增一个从UI到关系数据库的分支流程。其适用于逻辑不是 很复杂或者变化不会太大的稳定的应用系统开发。其不需要付出与业务无关的额外代价,并且在现代可见即可得的IDE 结合,能够很快的进行快速应用开发(RAD)。但是这种优势,也是其最大的劣势,程序中充满了IF-else, switch-case之类的逻辑或者大量的static的方法,每个功能都是一个程序分支,这对代码无法重用。 编码不易于维护,对复杂项目和变化需求不适应。

表驱动模式

表驱动模式为每个数据库表定义一个表模块类,包含操作该数据的所有行为方法。作为一个容器,将数据 和行为组织在一起。其对数据的粒度针对于数据表,而非数据行,因此需要以集合或者表(DataTable)传递数据信息。 表驱动模式基于对象但是完全由数据库驱动开发,在业务模型和数据库关系模型显著差异的情况下,应对需求, 并不是那么适合。但是在.net中提供的一些列如强类型DataSet等IDE的辅助下自动生成大量的代码, 也是一个不错的选择,因为部分数据库的操作趋于自动化。表驱动模式没有太过于关注业务逻辑,而是关注数据库 表结构。而业务逻辑和领域问题才是软件核心,所以对于复杂的场景也存在不能很好的胜任。

活动记录模式

活动记录模式是一个以数据库表一行Row为对象,并且对象中包含行为和数据的模式方法。其数据对象 很大程度的接近数据库表结构。在活动记录模式中对象通常也包含架构设计和扩展对象的CRUD(增删改查)的行为,以及数据验证等业 务规则。对于业务不是很复杂,对象关系与关系模型映射不具有很大差异情况,活动记录模式会运用的 很好。活动模式比较简单化设计,在现行的如Linq to sql,ActiveRecord框架, spring JDBC(Active Record),Ruby On Rails之类的框架之下, 将针对问题领域不是太过复杂的中小型项目十分有用,而且能做到快速,并且有较好设计的架构设计和扩展。 但是其模式和数据库表结构的过度的相互依赖,导致若你修改 数据库结构,你不得不同时修改对象以及相关逻辑。如果不能保证数据库关系模型和对象模式的很大程度 的相似这就进入的困境,无法解决对象和数据库结构的映射。

领域驱动设计(DDD)

在我们所述前面的几种架构模式都是在项目开始站在了以数据为中心的角度,而不是业务本身的问题领域。 而领域驱动设计模式是着重关注于系统的业务问题领域,首先开始为领域对象设计。与活动记录模式来说, 领域模型完全反映于问题领域业务概念模型逻辑,与数据库,持久化机制完成独立,其推崇持久化透明(POCO)。 其可以充分利用面向对象设计,不受持久化机制的任何约束。其为完全由业务驱动出来的。但是其最大的优势如上 各个模式一样也是其最大的劣势领域对象模型与关系模型具有天然的阻抗,领域实体对象早晚会需要映射到 持久化机制,有时我们必须等为了这种阻抗而让步。在当前有NHibearnate,EF,Fluent NHibearnate这类 ORM(Object Relation Mapping)框架辅助。

领域驱动设计是Eric Evans于2004年在《领域驱动设计:软件核心复杂性应对之道》 首先提出的,简称DDD.其实际为面向对象分析设计(OOAD)的延伸,利用面向对象思想进行分析设计,对系统逻辑严格 分层,对领域对象的的职责划分。领域逻辑按照职责和内聚的划分在不同的领域对象上,对象不再是单纯的数据载体,而应该 是一个具有行为,逻辑的富对象。

同时领域驱动设计中分层架构也是很重要的一部分,这是分离关注点(SOC)的体现。按照职责对每一层次划分。大体分为 表现层,业务逻辑层,仓储层,领域层,以及基本出设施层。以及后来所倡导的CQRS(读写分离).在层次之间为了应对面向对象设计 所有存在领域模型(DO),然而我们常用的存储却是关系类型(PO),所以还存在ORM(Object Relation Mapping).以及为了更好的 适应UI的表现存在视图对象(VO,有时我们简单VO的存在直接用数据传输对象(DTO)代替),所以存在DTO Mapping,在这里的VO或者DTO,往往 都是一层简单getter,setter的数据载体。

在领域驱动设计中还包含工作单元(UOW)),仓储,值类型,实体,聚合根,领域事件,领域边界,以及领域跟踪一类的概念,关于这些更多的知识 请参考《企业应用架构模式》,以及实现领域驱动设计

最后推荐最新的领域驱动设计书籍资料:此书被DDD鼻祖Eric Evans誉为继其开山之作后,近十年内第一本将DDD落到具体(接地气)的经典书籍。

实现领域驱动设计

[翻译]docker生态圈Mindmap

Docker是一个开源的Linux容器,其被业界所接受,很快成为了一个产业标准。Docker可以减少应用程序启动 时候的大量资源的筹备。docker很快的成为了新兴的应用程序容器标准。现在有很多项目正围绕着它处于开发中。 下面将是Mindmap出我所知道的一些项目。你可以在这里访问Mindmap.

docker mindmap

英语原文链接http://allthingsplatforms.com/platforms/docker-ecosystem-mapped-out/

自定义项目脚手架- Maven Archetypes

在上篇Intellij修改archetype Plugin配置 中我们已经简单介绍了关于archetype的作用。

简单来说maven archetype插件就是创建项目的脚手架,通过命令行或者IDE集成简化项目创建的工作。例如:

  • org.apache.maven.archetypes:maven-archetype-quickstart
  • org.apache.maven.archetypes:maven-archetype-site
  • org.apache.maven.archetypes:maven-archetype-webapp
  • 以及spring或者第三方提供了一些archetype plugin。

同时maven archetype插件也是一个简单的maven artifact,它包含了创建项目所需要的所有资源。 主要分为几类原型信息:

  • archetype描述文件(src/main/resources/META-INF/maven/archetype.xml),这为archetype 1.0, 包含所有创建项目的文件信息和路径信息。在(archetype 2.0)[http://maven.apache.org/archetype/maven-archetype-plugin/]增加了更灵活的archetype-metadata.xml(src/main/resources/META-INF/maven/下), archetype元数据信息,并且完全支持1.0.
  • 项目的原型文件(src/main/resources/archetype-resources/之下),将会被archetype插件 copy到项目目录结构去。
  • 创建项目的pom文件(src/main/resources/archetype-resources下)
  • archetype pom文件,在archetype项目根目录下。

创建archetype插件

  1. 首先在archetype中加入一个pom文件,如下:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>com.github.greengerong</groupId>
          <artifactId>component</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <packaging>jar</packaging>
    
          <name>component</name>
          <url>http://maven.apache.org</url>
    
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
    
          <dependencies>
          </dependencies>
    </project>
    
  2. 创建archetype-metadata.xml,位于src/main/resources/META-INF/maven/目录下,例如:

    <?xml version="1.0" encoding="UTF-8"?>
    <archetype-descriptor name="app-server">
        <fileSets>
            <fileSet filtered="true" encoding="UTF-8">
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.**</include>
                </includes>
            </fileSet>
            <fileSet filtered="true" encoding="UTF-8">
                <directory>src/test/java</directory>
                <includes>
                    <include>**/*.**</include>
                </includes>
            </fileSet>
        </fileSets>
    </archetype-descriptor>
    

更多配置信息参考archetype-descriptor.

  1. 为将创建的项目增加pom.xml文件,以${artifactId} / ${groupId} 变量作为占位符,例如:

      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>${groupId}</groupId>
          <artifactId>${artifactId}</artifactId>
          <version>${version}</version>
          <packaging>jar</packaging>
    
          <name>${artifactId}</name>
    
          <properties>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
    
          <dependencies>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.11</version>
                  <scope>test</scope>
              </dependency>
          </dependencies>
    
          <build>
              <pluginManagement>
                  <plugins>
                      <plugin>
                          <artifactId>maven-compiler-plugin</artifactId>
                          <configuration>
                              <source>1.6</source>
                              <target>1.6</target>
                          </configuration>
                      </plugin>
                  </plugins>
              </pluginManagement>
          </build>
      </project>
    
  2. 接下来在archetype项目下install plugin:mvn clean install.

  3. 利用已有archetype plugin创建项目:

    命令行:

    mvn archetype:generate -DarchetypeGroupId=<archetype-groupId>  -DarchetypeArtifactId=<archetype-artifactId> -DarchetypeVersion=<archetype-version>  -DgroupId=<my.groupid> -DartifactId=<my-artifactId>
    

intellij选择增加archetype plugin: /images/blog_img/Intellij-archetype-plugin.png

对于删除intellij测试archetype信息,请参见Intellij修改archetype Plugin配置.

注意:

  • 如果你也需要文件名字或者目录名字,则需要用特殊变量__artifactId__(双下划线)作为占位符。

    本文demo,请移到到github maven-archetypes-demo.

Intellij修改archetype Plugin配置

Maven archetype plugin为我们提供了方便的创建 project功能,Archtype指我们项目的骨架,作为项目的脚手架。 如fornt end的yo之类。我们能够通过简单的一行控制台command line创建你所需要的项目结构:

mvn archetype:generate
  -DarchetypeGroupId=<archetype-groupId>
  -DarchetypeArtifactId=<archetype-artifactId>
  -DarchetypeVersion=<archetype-version>
  -DgroupId=<my.groupid>
  -DartifactId=<my-artifactId>

常用的Maven archetype plugin有:

  • org.apache.maven.archetypes:maven-archetype-quickstart
  • org.apache.maven.archetypes:maven-archetype-site
  • org.apache.maven.archetypes:maven-archetype-webapp
  • 以及spring或者第三方提供了一些archetype plugin。

同时在java世界强大的IDE Intellij也支持按照maven archetype 创建java项目。你只需要选择maven 创建项目,在最后选择你希望的 archetype plugin,然后就可以喝杯coffe等待build success。

关于如何自定义项目的Maven archetype plugin,博主将会 在后续的文章介绍。在此次博文将是记录如果去掉你手动在intellij中添加 的archetype plugin。

mac版本,你可以找到文件:

~/Library/Caches/IntelliJIdea<version>/Maven/Indices/UserArchetypes.xml

然后用你喜欢的编辑器打开它(例如博主当前使用的Atom):

<?xml version="1.0" encoding="UTF-8"?>
<archetypes>
<archetype groupId="com.github.greengerong" artifactId="components-archetype" version="1.0.0" />
</archetypes>

你可以在xml的archetypes节点增加或者删除修改配置,然后重新启动你的Intellij。

Ng-template寄宿方式

如果你是一个angular的开发者的话,对于ng-html2js你应该 很熟悉。对于angular的指令,我们经常需要定义模板( directive template/templateUrl),你可以选择讲html page 放在真正的的web容器中寄宿,也可以选择angular的ng-template 放在view的page之上,抑或也可以讲html打成一个js文件和directive 的js文件合并在一起发布。

  • 对于直接寄宿在web容器.

    这很简单,直接放在jetty,tomcat,iis, 抑或node express public目录下。这里没什么可以多说的,所以我们跳过。

  • angular ng-template模板:

    代码如下:

        <script type="text/ng-template" id="/tpl.html">
    
          Content of the template.
    
        </script>
    

    这将会在angular的compile时候解析,angular将会把它放在angular的$templateCache 中。

    对于$templateCache,如其名 这是angular对模板的缓存的service。在启用了$templateCache的$http ajax请求, angular将会首先在$templateCache中查找是否有对此url的缓存:

          $templateCache.get('templateId.html')
    

    如果存在缓存,着angular将会直接用缓存中获取,并不会在发送一次ajax。 对于所有的指令和模板angular默认启用了templateCache。

    这在于angular所处理的模式开发很有关系的。我们经常提到的SPA(single page application) 我们把view的显示处理等表现逻辑推到了前段,而后端提供只与数据有关的soap/restful service 这样对于一个应用程序业务逻辑来说不变的是处理数据的业务逻辑,这份逻辑你可以共享在不管前端是mobile app 或者是浏览器,或者winform gui的图形化程序,因为对于同一个业务这是不变的。将view的分离推到各自的客户端 将是更好的解决方案。

    回到angular $templateCahce,对于一个应用程序view的分离,之后在对于当前的应用程序平台,html/js/css 这类资源是静态的,最好是不变的,那么你可以自由的缓存在客户端,减少服务器的交互,以及为了更大的性能追求,我们 可以把这类静态资源放在Nginx这里反向代理或者CDN上,让我们的程序获取更大的性能和扩展空间。

  • 回到angular的ng-html2js:

    有了上边对于$templateCache的理解,那你应该很容易理解html2js的方式了,与ng-template不同的 是ng-template是angular在compile的时候自动加入$templateCache的,html2js是我们在开发 时候利用build自己放入$templateCache。

      angular.module('myApp', [])
      .run(function($templateCache) {
          $templateCache.put('templateId.html',
              'This is the content of the template'
          );
      });
    

形如上面的输出,将html文件打成一个js文件。

这你也许在angular的单元测试karma unit test中看见过, karma-ng-html2js-preprocessor ,还有如果你也希望在build时候做到如此,那么你可以使用grunt plugin grunt-html2js.

但使用grunt plugin的前提是你在你的项目中引入的grunt build的work flow,那么你可以在gruntfile.js中几行代码轻松的搞定。但是如果 你和我一样使用的是java的maven或者是gradle 作为build,那么你可以尝试博主的maven pluginnghtml2js. 使用方式如下:

<plugin>
    <groupId>com.github.greengerong</groupId>
    <artifactId>nghtml2js</artifactId>
    <version>0.0.3</version>
    <configuration>
        <module>demo.template</module>
        <html>${project.basedir}</html>
        <extensions>
            <param>tpl</param>
            <param>html</param>
        </extensions>
    </configuration>
    <executions>
        <execution>
            <id>nghtml2js</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

微软开放了.NET 4.5.1的源代码

.NET Reference Source发布了beta版,可以在线浏览.NET Framework 4.5.1的源代码,并且可以通过配置,在Visual Studio 2013中调试.NET Framework

.NET Framework团队的项目经理Alok Shriram在.NET Framework的博客中撰文介绍了最新的.NET Reference Source。它不仅可以用来浏览最新的.NET代码,而且通过强大的Roslyn,改善了“Go to Defination”和“Finding Reference”的在线体验。此外,开发者还可以下载整个源代码包,在Visual Studio中打开并浏览。使用Schabse Laks编写的插件,可以在VS中直接导航到该网站。

通过简单的配置,开发者可以在Visual Studio中单步调试.NET Framework 4.5.1及其后续补丁和更新。这无疑是广大开发者翘首以盼的功能。启动Visual Studio 2013,打开Tools –> Options –> Debugging –> General菜单,进行如下配置:

  • 禁用Just My Code
  • 禁用Step over properties and operators
  • 禁用Require source files to exactly match the original version
  • 选中Enable .NET Framework source stepping
  • 选中Enable source server support

这样,开发者在调试代码时,就可以通过F11直接进入.NET代码。

这并不是微软第一次开放.NET Framework的源代码。早在2007年,他们就第一次公布了.NET 3.5的源代码并支持Visual Studio调试。并且对于后续的4.0和4.5,也都提供了相应的源代码包。但当框架升级时,这些旧的包就变得毫无价值了。从4.5.1开始,.NET Framework团队彻底改变了符号索引和发布的过程,使其与构建过程同步,这样在新版本发布时,相应的PDB文件也会从Reference Source网站进行更新。

目前的beta站点只是临时的,届时将会取代现在的旧站点。后续微软也会将还未公布源代码的程序集逐步添加进来。新的.NET Reference Source许可协议回归到了MS-RSL,这也使得像Mono这样的团队可以放心大胆地使用。

正如Scott Hanselman所说:微软可能并不是开源的,但却绝对是源代码开放的。

Infoq原文链接:http://www.infoq.com/cn/news/2014/02/microsoft-net-source-code?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term=global&utm_reader=feedly.

nodejs获取客户端IP Address

在网上看见很多问node.js如何获取客户端IP,所以记录下来,以供大家参考。

    function getClientIp(req) {
        return req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
    };

代码,第一段判断是否有反向代理IP(头信息:x-forwarded-for),在判断connection的远程IP,以及后端的socket的IP。

细说angular Form addControl方法

在本篇博文中,我们将接触angular的验证。angular的验证是由form 指令和ngModel协调完成的。今天博主在这里想要说的是在验证在的一种特殊情况,当验证控件没有没有name属性这是不会被form捕获的。或者是你希望在ngRepeat中使用动态表达式。

下面且让我们先来从angular源码中看起如下:

首先是ngModel:

    var ngModelDirective = function() {
      return {
        require: ['ngModel', '^?form'],
        controller: NgModelController,
        link: function(scope, element, attr, ctrls) {
          // notify others, especially parent forms

          var modelCtrl = ctrls[0],
              formCtrl = ctrls[1] || nullFormCtrl;

          formCtrl.$addControl(modelCtrl);

          scope.$on('$destroy', function() {
            formCtrl.$removeControl(modelCtrl);
          });
        }
      };
    };

从上面我们能够看出ngModel指令会在编译时期的post link阶段会通过form的 addControl方法把自己的controller注册到父节点上的form的formController中。

在看看ngModel controller初始化代码:

    var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
        function($scope, $exceptionHandler, $attr, $element, $parse) {
     ....
      this.$pristine = true;
      this.$dirty = false;
      this.$valid = true;
      this.$invalid = false;
      this.$name = $attr.name;

我们从上面我们可以看到 ngModel的$name属性并不支持表达式计算

而FormController的addControl代码则是:

    /**
     * @ngdoc function
     * @name ng.directive:form.FormController#$addControl
     * @methodOf ng.directive:form.FormController
     *
     * @description
     * Register a control with the form.
     *
     * Input elements using ngModelController do this automatically when they are linked.
     */
    form.$addControl = function(control) {
      // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
      // and not added to the scope.  Now we throw an error.
      assertNotHasOwnProperty(control.$name, 'input');
      controls.push(control);

      if (control.$name) {
        form[control.$name] = control;
      }
    };

从上面我们可以清楚的看见虽然ngModel注册了自己,但是这里也不一定能注册成功,ngModel心必须有自己的$name才能被注册成功

从上面的代码中可以得出,当我们的验证失效的时候,我们可以有一个万能的方式就是 手动注册到form controller

手动注册form controller

为了我写了一个dy-name的插件,其会在post link阶段解析表达式,并把自己注册到父form controller。

如下:

    .directive("dyName", [

        function() {
          return {
            require: "ngModel",
            link: function(scope, elm, iAttrs, ngModelCtr) {
              ngModelCtr.$name = scope.$eval(iAttrs.dyName)
              var formController = elm.controller('form') || {
                $addControl: angular.noop
              };
              formController.$addControl(ngModelCtr);

              scope.$on('$destroy', function() {
                formController.$removeControl(ngModelCtr);
              });

            }
          };
        }
      ])

使用方式:

    <div ng-repeat="item in demo.fields">
      <div class="control-group">
        <label class="control-label"> : </label>
        <div class="controls">
          <input type="number"  dy-name="item.field" ng-model="demo.data[item.field]" min="10" max="500" ng-required="true"/>
        </div>
      </div>
    </div>

其实实现为在post link阶段获取到form controller,并把自己注册到form controller,而且为了消除对象的级联,将会在scope摧毁阶段remove自己。

其效果请看jsbin $addControl

注意:在formController.$addControl方法的参数传入的不是界面控件,而是ngModelController.或者名字为$addController更合适。