Getting started

This text will offer you the basics to get started with mu.semte.ch.  The theory is explained in An Ecosystem of User-facing Microservices supported by Semantic Models, this guide is for the practically oriented.

Installation

All mu.semte.ch applications rely on Docker and Docker Compose.  Install those following the respective links.

Each microservice, and all related tooling, is offered in the form of Docker images.

When starting a new project, a good basis is https://github.com/mu-semtech/mu-project.  Clone this repository, or copy its contents, as it will be the basis for this tutorial.

git clone https://github.com/mu-semtech/mu-project.git

Using microservices

Suppose we want to reuse the madnificent/books-service.  This service, like all other publicly available services, should be hosted on Docker Hub.  The Docker image can be found at https://hub.docker.com/r/madnificent/books-service.  We’ll include this service, and proxy the relevant requests to our newly imported service.

Include books-service

We add the microservice in the docker-compose.yml file of our mu-project.  Just above the resource line, we can add the following:

=    volumes:
=      - ./data/db:/data
+    books:
+      image: madnificent/books-service:2.0.0
+      links:
+        - db:database
=  resource:
=    image: semtech/mu-cl-resources:1.18.0

We also ensure the dispatcher knows the books service, so we can use it to forward the request later.  This is done in the section on the dispatcher:

=    links:
=      - resource:resource
+      - books:books
=    volumes:

Dispatch books & authors requests

In the config/dispatcher/dispatcher.ex file, all proxy rules are defined.  They’re matched top to bottom of the file.  You can copy one of the examples to dispatch a new request.

The proxy service needs to know the books service.  See the previous section for the necessary link.

We add a route for “/books/” and for “/authors/” like so:

=  # match "/themes/*path" do
=  #   Proxy.forward conn, path, "http://resource/themes/"
=  # end
=
+  match "/books/*path" do
+    Proxy.forward conn, path, "http://books/books/"
+  end
+
+  match "/authors/*path" do
+    Proxy.forward conn, path, "http://books/authors/"
+  end
+
=  match _ do
=    send_resp( conn, 404, "Route not found.  See config/dispatcher.ex" )
=  end

Ember in the frontend

Our end-users access the services through EmberJS application.  This provides us with an integrated, styled and flexible view of the enabled microservices.  We’ll create a new ember application to allow end-users to list, create, and delete authors.  The advised way to build and develop EmberJS applications is using ember-cli.

You can install ember-cli from ember-cli.com, or you can use the ember-docker found at https://github.com/madnificent/docker-ember .  Our examples assume you’ll use ember-docker.

Build a new app

First we create the new application.  The command is short, but it may take a while to fetch all NPM dependencies.  Grab a coffee while the computer works for you.

edi ember new books

Live reloading changes

Let’s see if our new application runs.  Go into the books directory and run the ember serve command (available as eds).  Once the files have compiled, you can visit the site in your browser at localhost:4200.

cd books
eds --proxy http://host # alt: ember serve --proxy http://localhost:80/

The proxy connects to our localhost on port 80 (yes, it’s called host in the ember-docker, rather than localhost).  We’ll use this later to fetch content from the microservices.  Let’s alter the title of our application, the browser’s view will update automatically.  Open app/application.hbs and change the following:

- {{!-- The following component displays Ember's default welcome message. --}}
- {{welcome-page}}
- {{!-- Feel free to remove this! --}}
+  <h2 id="title">My books</h2>

Boom, automatic updates in the browser.

Connecting

EmberJS applications roughly follow the Web-MVC pattern.  The applications have a rigid folder-structure, most content being in the app folder.  Ember-cli uses generators to generate basic stubs of content.  We create the books model, route and controller using ember-cli.  Check the helpers for ember generate model, ember generate route and ember generate controller, or the following:

edi ember generate model book title:string isbn:string
edi ember generate route book
edi ember generate controller book

The terminal output shows the created and updated files.  (note: generating new files can make watched files fail in Docker, just kill and restart eds should that happen.)

We will fetch all books and render them in our template.  In routes/book.js

=  export default class BooksRoute extends Route {
+    model(){
+      return this.store.findAll('book');
+    }
=  }

We’ll display the found records in our template so we’re able to see the created records later on.  Add the following to templates/book.hbs

+  <ul>
+    {{#each @model as |book|}}
+      <li>{{book.title}} <small>{{book.isbn}}</small></li>
+    {{/each}}
+  </ul>

Creating new books

We’ll add a small input-form through which we can create new books at the bottom of our listing.  Two input fields and a create button will suffice for our example.

In the app/templates/book.hbs template, we’ll add our create fields and button:

+  <hr />
+ <form {{on "submit" this.createBook}} >
+   <dl>
+     <dt>Book title</dt>
+     <dd>
+        <Input @value={{this.newTitle}}
+               @placeholder="Thinking Fast and Slow" />
+     </dd>
+     <dt>ISBN</dt>
+     <dd>
+       <Input @value={{this.newIsbn}}
+              @placeholder="978-0374533557" />
+     </dd>
+   </dl>
+   <button type="submit">Create</button>
+ </form>

We’ll add this action in the controller and make it create the new book.  In app/controllers/book.hbs add the following:

=  import Controller from '@ember/controller';
+  import { action } from '@ember/object';
+  import { tracked } from '@glimmer/tracking';
+  import { inject as service } from '@ember/service';
=
=  export default class BooksController extends Controller {
+    @tracked newTitle = '';
+    @tracked newIsbn = '';
+
+    @service store;
+
+    @action
+    createBook(event) {
+      event.preventDefault();
+      // create the new book
+      const book = this.store.createRecord('book', {
+        title: this.newTitle,
+        isbn: this.newIsbn
+      });
+      book.save()
+      // clear the input fields
+      this.newTitle = '';
+      this.newIsbn = '';
+    }
=  });

Removing books

Removing books follows a similar path to creating new books.  We add a delete button to the template, and a delete action to the controller.

In app/templates/book.hbs we alter:

= <ul>
=  {{#each @model as |book|}}
+    <li>
+      {{book.title}}<small>{{book.isbn}}</small>
+      <button {{on "click" (fn this.removeBook book)}}>Remove</button>
+    </li>
=  {{/each}}
= </ul>

In app/controllers/book.hbs we alter:

=      this.newTitle = '';
=      this.newIsbn = '';
=    }
+
+    @action
+    removeBook( book, event ) {
+      event.preventDefault();
+      book.destroyRecord();
=    }
=  }