Angular pluggable architecture?

Pluggable Architecture – A Software Architecture that allows to plug functionality using PluggableModules. Ward Cunningham

One of the projects I am involved in these days is composed of multiple applications each of them with its own front-end implemented in Angular, all using consistent UX patterns and framework. The common user flows like managing projects or navigating to the applications is handled in a central application. At the same time, common components like navbar or sidebars are reused across.

This front-end separation by applications or large features has prevented us from creating a big monolithic web app and our teams have the kind of independence and control in the front-end we get in the back-end with micro-services.

All good so far, but let’s say that now we want to have a dashboard application composed of widgets and one of the requirements is that multiple teams can implement and deploy their own widgets independently. Hence, without the need of modifying the dashboard application code or redeploying it when a new widget comes or gets updated.

Can an Angular CLI application load and render external widgets at runtime?

The short answer is yes, it is possible. You can find a working demo in this repo https://github.com/paucls/angular-pluggable-architecture

The long answer is that there is not a straightforward solution ;-), it took me a little bit of research to find out how.

First of all, the application needs a way to load remote widget bundles at runtime. This sounds like lazy loading, but that is not going to help us, lazy loading is for loading modules linked to child routes, but the modules still need to belong to the same application and be known at build-time.

The solution is to use SystemJS, but only at runtime and only to load the widgets. We do not need to configure our CLI project to switch from Webpack to SystemJS.

Loading a bundle will look like this:

const module = await SystemJS.import('/path-to-a-module-bundle');

The next problem is that the bundle contains a Module and a Component for the widget but they have dependencies themselves to other modules. The smallest component has at least dependencies to @angular/core.

import { Component, OnInit } from '@angular/core';

To stop SystemJS from trying to find and fetch those vendor modules from the network we need to set SystemJS with the modules already available.

/**
 * Set existing vendor modules into SystemJS registry.
 * This way SystemJS won't make HTTP requests to fetch imported modules
 * needed by the dynamicaly loaded Widgets.
 */
import { System } from 'systemjs';
declare const SystemJS: System;

import * as angularCore from '@angular/core';
import * as angularCommon from '@angular/common';
import * as angularCommonHttp from '@angular/common/http';

SystemJS.set('@angular/core', SystemJS.newModule(angularCore));
SystemJS.set('@angular/common', SystemJS.newModule(angularCommon));
SystemJS.set('@angular/common/http', SystemJS.newModule(angularCommonHttp));

The second part is compiling and rendering each widget. For this, we need to make use of the Angular JIT compiler to compile the Angular module and component of the widget. And then create the widget component in ViewChield target div.

@Component({
  selector: 'app-dashboard',
  template: '<div #content></div>'
})
export class DashboardComponent implements AfterViewInit {

  @ViewChild('content', { read: ViewContainerRef }) content: ViewContainerRef;

  constructor(private compiler: Compiler, private dashboardService: DashboardService,
    private injector: Injector) { }

  ngAfterViewInit() {
    this.loadWidgets();
  }

  private async loadWidgets() {
    const widgets = await this.dashboardService.getWidgetConfigs().toPromise();
    widgets.forEach((widget) => this.createWidget(widget));
  }

  private async createWidget(widget: WidgetConfig) {
    // import external module bundle
    const module = await SystemJS.import(widget.moduleBundlePath);

    // compile module
    const moduleFactory = await this.compiler.compileModuleAsync(module[widget.moduleName]);

    // resolve component factory
    const moduleRef = moduleFactory.create(this.injector);
    const componentProvider = moduleRef.injector.get(widget.name);
    const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(componentProvider);

    // compile component
    this.content.createComponent(componentFactory);
  }

}

Note that to be able to use the JIT compiler our build needs to have AOT disabled what it is not ideal. There is an open issue (https://github.com/angular/angular/issues/20875) to allow the use of the compiler with AOT, I tried some of the temporal solutions suggested like defining custom providers for JIT in the app module but I could not make it work yet.

References:
– Developing with Angular, Denys Vuika. https://github.com/DenisVuyka/developing-with-angular/tree/master/angular/plugins
– As busy as a bee — lazy loading in the Angular CLI, David Herges https://blog.angularindepth.com/as-busy-as-a-bee-lazy-loading-in-the-angular-cli-d2812141637f … how does lazy loading in the Angular CLI work under the hood?
– How to load dynamic external components into Angular application? https://stackoverflow.com/questions/45503497/how-to-load-dynamic-external-components-into-angular-application/45506470#45506470
– Here is what you need to know about dynamic components in Angular, Maxim Koretskyi https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-angular-ac1e96167f9e
– Modules are not what you think they are, Maxim Koretskyi https://youtu.be/pERhnBBae2k
– Extension mechanism demo https://github.com/maximusk/extension-mechanism-demo
– JIT Compiler needed with AOT Build for Dynamic Component. https://github.com/angular/angular/issues/20875

Advertisements

DDD Building Blocks – Value Objects

Value objects are one of the foundational building blocks of Domain-Driven Design. They allow modelling concepts of the Ubiquitous Language that describe, measure or quantify other things in the domain.

It is crucial to understand their characteristics and make more use of them whenever possible instead of trying to model every concept as an Entity or worse, having an Anemic Domain Model where logic related to a value leaks and spreads around services or client code. Values help separating concerns and they are easier to implement, test and maintain.

Examples of Value Objects in a domain could be colour, money, address, e-mail, coordinate, etc. Note that a concept can be an Entity in one domain model and a Value Object in other.

Value Object Characteristics

– Inmutable Conceptual Whole
– Describes, Measures or Quantifies an Entity
– Does not have a unique identity
– Structural equality
– Replaceability
– Side-Effect-Free Behaviour

Implementing Value Objects

Constructors

Value objects are inmutable, do not implement setter methods for its properties, instead provide a constructor with all parameters necessary to initialize its intrinsic state. Parameters may be other objects used to derive internal properties on the new Value.

public class Money {
    private BigDecimal amount;
    private String currency;

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public String getCurrency() {
        return currency;
    }
...

You can find useful, especially for testing immutability, to provide a copy constructor.

    public Money(Money money) {
        this.amount = money.amount;
        this.currency = money.currency;
    }

Use Guard Clauses in the constructor to ensure that the parameters passed are not only valid individually also as a whole initialize a valid Value.

public class FullName {
    private String firstName;
    private String lastName;

    public FullName(String firstName, String lastName) {
        if (firstName == null || lastName == null) {
            throw new IllegalArgumentException("First and last name are required.");
        }

        this.firstName = firstName;
        this.lastName = lastName;
    }
...

Self-encapsulation/delegation

Value objects must not expose setter methods, but internally, we may use and delegate to them to simplify constructors.

Continue reading “DDD Building Blocks – Value Objects”

Developing with a Stub backend on Angular 2

In Angular 1 I always use during development a stub or mock REST API backend that allows me running the application without the real API. In this way, the front-end implementation can be started before or in parallel with the backend. It is also useful for small prototypes or demos.

You could quickly implement a fake backend using Node or something similar, but I recommend to mock the backend directly in Angular. It is faster because the application won’t hit the wire and what I really like is that it is stateless (every time the browser is reloaded the stub data is reinitialized).

Those two points are great for local development but what I find fundamental is to use it for running protractor E2E tests, that way I always can rely on a backend substitute that does not keep state between test (protractor reloads the browser before every test, and again the stub data gets reinitialized).

This post shows how to configure a stub backend on Angular 2 using the MockBackend test class.
On develop mode we will override the Http provider to, instead of using a real backend implementation such as XHRBackend, use the MockBackend.

I define the stub backend provider in a separate file stub-backend-provider.ts, here is all the magic happens swapping the backend provider for develop mode and defining stub responses for determined HTTP requests.

src/app/stub-backed/stub-backend-provider.ts

import { Http, BaseRequestOptions, Response, ResponseOptions, RequestMethod, XHRBackend } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';

import { environment } from '../../environments/environment';
import { Task } from '../tasks-list/task';
import { generateUuid, getUuidFromUrl } from './stub-backend-utils';

/**
 * Provider to allow the use of a stub backend instead of a real Http service for backend-less development.
 */
export let stubBackendProvider = {
  provide: Http,
  deps: [MockBackend, BaseRequestOptions, XHRBackend],
  useFactory: (mockBackend: MockBackend, options: BaseRequestOptions, realBackend: XHRBackend) => {

    if (!environment.stubBackend) {
      console.log('Configuring real Http backend...');
      return new Http(realBackend, options);
    }

    console.log('Configuring stub Http backend...');

    let tasks: Task[] = [
      {id: '9509c8b4-ad34-4378-b49c-c9206dfd7f75', name: 'Buy milk', done: false, userId: 'user-1'},
      {id: '1b35d8f8-9e80-4316-b3e3-135a8f81200f', name: 'Pay rent', done: true, userId: 'user-1'}];

    mockBackend.connections.subscribe((connection: MockConnection) => {

      // wrap in timeout to simulate server api call
      setTimeout(() => {

        // Get all tasks
        if (connection.request.method === RequestMethod.Get && connection.request.url.match('/tasks$')) {
          connection.mockRespond(new Response(new ResponseOptions({body: tasks.slice()})));
          return;
        }

        // Save task
        if (connection.request.method === RequestMethod.Post && connection.request.url.match('/tasks$')) {
          let newTask = JSON.parse(connection.request.getBody());
          newTask.id = generateUuid();
          tasks.push(newTask);

          connection.mockRespond(new Response(new ResponseOptions({body: newTask})));
          return;
        }

        // Delete task
        if (connection.request.method === RequestMethod.Delete && connection.request.url.match('/tasks/*')) {
          let id = getUuidFromUrl(connection.request.url);
          tasks = tasks.filter(task => task.id !== id);

          connection.mockRespond(new Response(new ResponseOptions()));
          return;
        }
      }, 500);

    });

    return new Http(mockBackend, options);
  }
};

The provider requires uses a new Angular CLI environment property that I called stubBackend, with a value of true on develop and false on production.
/src/environments/environment.ts

export const environment = {
  production: false,
  stubBackend: true
};

/src/environments/environment.prod.ts

export const environment = {
  production: false,
  stubBackend: false
};

Continue reading “Developing with a Stub backend on Angular 2”

Unit test exception messages using JUnit ExpectedException Rule

Traditionally JUnit supported expected exceptions using the @Test annotation like this

@Test(expected = ParsingException.class)
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   // Act
   gameJsonParser.parseGame(bufferedReaderMock);
}

This has two inconveniences. The expectation should be defined outside the test body and it does not support asserting the exception message.

A workaround commonly used is handling the exception with a try/catch block and assert it manually.

@Test
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   // Act
   try {
     gameJsonParser.parseGame(bufferedReaderMock);
     fail("Should have thrown ParsingException but did not!");
   }
   catch(ParsingException e) {
     String message = "Parsing failed. Empty game file.";
     assertEquals(message, e.getMessage());
   }
}

As you can see that brings lot of boilerplate code to the test.

Since JUnit 4.7 we can use the ExpectedException core rule to assert exceptions in a more natural flow and also to assert their messages.

ExpectedException: Allows a test to specify expected exception types and messages in the test itself.

@Rule
public ExpectedException exception = ExpectedException.none();

@Test
public void parseGame_when_fileIsEmpty_then_throwsParsingException() throws Exception {
   // Arrange
   BufferedReader bufferedReaderMock = mock(BufferedReader.class);
   when(bufferedReaderMock.readLine()).thenReturn("");

   exception.expect(ParsingException.class);
   exception.expectMessage("Parsing failed. Empty game file.");

   // Act
   gameJsonParser.parseGame(bufferedReaderMock);
}

References:
https://paucls.wordpress.com/2013/11/12/using-junit-rules
https://www.infoq.com/news/2009/07/junit-4.7-rules