Category Archives: Java

Ice cream sales break microservices, Hystrix to the rescue

In November 2015, we had the opportunity to spend three days with a greenfield project in order to get to know Spring Cloud Netflix. At comSysto, we always try to evaluate technologies before their potential use in customer projects to make sure we know their pros and cons. Of course, we had read about several aspects, but we never really got our hands dirty using it. This had to change!

Besides coming up with a simple scenario that can be completed within a few days, our main focus was on understanding potential problems in distributed systems. First of all, any distributed system comes with the ubiquitous problem of failing services that should not break the entire application. This is most prominently addressed by Netflix’ “Simian Army” which intentionally breaks random parts of the production environment.

However, we rather wanted to provoke problems arising under heavy load due to capacity limitations. Therefore, we intentionally designed a distributed application with a bottleneck that turned into an actual problem with many simultaneous requests.

Our Use Case

Our business case is about an ice selling company, which is acting on worldwide locations. On each location there are ice selling robots. At the company’s headquarters we want to show an aggregated report about the ice selling activities for each country.

All our components are implemented as dedicated microservices using Spring Boot and Spring Cloud Netflix. Service discovery is implemented using Eureka server. The communication between the microservices is RESTful.

architecture

Architecture overview of our distributed system with the deployment setup during the experiments.

There is a basic location-service, which knows about all locations provided with ice-selling-robots. The data from all these locations has to be part of the report.

For every location, there is one instance of the corresponding microservice representing an ice-selling-robot. Every ice-selling-robot has locally stored information about the amount of totally sold ice cream and the remaining stock amount. Each of them continuously pushes this data to the central current-data-service. It fails with a certain rate, which is configured by a central Config Server.

For the sake of simplicity, the current-data-service stores this information in-memory. Every time it receives an update from one of the ice-selling-robots, it takes the new value and forgets about the old one. Old values are also forgotten if their timestamp is too old.

The current-data-service offers an interface by which the current value for the totally sold amount of ice cream or the remaining stock amount can be retrieved for one location. This interface is used by an aggregator-service, which is able to generate and deliver an aggregated report on demand. For all locations provided by the location-service the current data is retrieved from the current-data-service, which is then aggregated by summing up the single values from the locations grouped by the locations’ country. The resulting report consists of the summed up values per country and data type (totally sold ice cream and remaining stock value).

Because the connection between aggregator-service and current-data-service is quite slow, the calculation of the report takes a lot of time (we simply simulated this slow connection with a wifi connection, which is slow in comparison with an internal service call on the same machine). Therefore, an aggregated report cache has been implemented as fallback. Switching to this fallback has been implemented using Hystrix. At fixed intervals the cache is provided with the most current report by a simple scheduled job.

The reporting service is the only service with a graphical user interface. It generates a very simplistic html-based dashboard, which can be used by the business section of our company to get an overview of all the different locations. The data presented to the user is retrieved from the aggregator-service. Because this service is expected to be slow and prone to failure, a fallback is implemented which retrieves the last report from the aggregated-report-cache. With this, the user can always request a report within an acceptable response time even though it might be slightly outdated. This is a typical example for maintaining maximum service quality in case of partial failure.

report

The reporting “dashboard”.

We used a Spring Cloud Dashboard from the open source community for showing all registered services:

cloud-dashboard

Spring Cloud Dashboard in action.

The circuit-breaker within the aggregator-service can be monitored from Hystrix dashboard.

Screen Shot 2015-12-30 at 22.22.26

Hystrix dashboard for reporting service under load. All circuits are closed, but 19% of all getReport requests failed and were hence successfully redirected to the cached version.

Understanding the Bottleneck

When using Hystrix, all connectors to external services typically have a thread pool of limited size to isolate system resources. As a result, the number of concurrent (or “parallel”) calls from the aggregator-service to the report-service is limited by the size of the thread pool. This way we can easily overstress the capacity for on-demand generated reports, forcing the system to fall back to the cached report.

The relevant part of the reporting-service’s internal declaration looks as depicted in the following code snippet (note the descriptive URLs that are resolved by Eureka). The primary method getReport() is annotated with @HystrixCommand and configured to use the cached report as fallbackMethod:

@HystrixCommand(
 fallbackMethod="getCachedReport",
 threadPoolKey="getReportPool"
)
public Report getReport() {
 return restTemplate.getForObject("http://aggregator-service/", Report.class);
}

public Report getCachedReport() {
 return restTemplate.getForObject("http://aggregated-report-cache/", Report.class);
}

In order to be able to distinguish primary and fallback calls from the end user’s point of view, we decided to include a timestamp in every served report to indicate the delta between the creation and serving time of a report. Thus, as soon as the reporting-service delegates incoming requests to the fallback method, the age of the served report starts to increase.

Testing

With our bottleneck set up, testing and observing the runtime behavior is fairly easy. Using JMeter we configured a testing scenario with simultaneous requests to the reporting-service.

Basic data of our scenario:

  • aggregation-server instances: 1
  • test duration: 60s
  • hit rate per thread: 500ms
  • historize-job-rate: 30s
  • thread pool size for the getReport command: 5

Using the described setup we conducted different test runs with a JMeter thread pool size (=number of concurrent simulated users) of 3, 5 and 7. Analyzing the served reports timestamps leads us to the following conclusion:

Using a JMeter thread count below the size of the service thread pool results in a 100% success rate for the reporting-service calls. Setting sizes of both pools equal already gives a small noticeable error rate. Finally, setting the size higher than the thread pool results in growing failures and fallbacks, also forcing the circuit breaker into short circuit states.

Our measured results are as follows (note that the average report age would be 15s when always using the cached version given our historize-job-rate of 30s):

  • 3 JMeter threads: 0,78s average report age
  • 5 JMeter threads: 1,08s average report age
  • 7 JMeter threads: 3,05s average report age

After gaining these results, we changed the setup in a way that eliminates the slow connection. We did so by deploying the current-data-service to the same machine as the aggregation-service. Thus, the slow connection has now been removed and replaced with an internal, fast connection. With the new setup we conducted an additional test run, gaining the following result:

  • 7 JMeter threads, fast network: 0,74s average report age

By eliminating one part of our bottleneck, the value of report age significantly drops to a figure close below the first test run.

Remedies

The critical point of the entire system is the aggregation due to its slow connection. To address the issue, different measures can be taken.

First, it is possible to scale out by adding additional service instances. Unfortunately, this was hard to test given the hardware at hand.

Second, another approach would be to optimize the slow connection, as seen in our additional measurements.

Last but not least, we could also design our application for always using the cache assuming that all users should see the same report. In our simplistic scenario this would work, but of course that is not what we wanted to analyze in the first place.

Our Lessons Learned

Instead, let us explain a few take-aways based on our humble experience of building a simple example from scratch.

Spring Boot makes it really easy to build and run dozens of services, but really hard to figure out what is wrong when things do not work out of the box. Unfortunately, available Spring Cloud documentation is not always sufficient. Nevertheless, Eureka works like a charm when it comes to service discovery. Simply use the name of the target in an URL and put it into a RestTemplate. That’s all! Everything else is handled transparently, including client-side load balancing with Ribbon! In another lab on distributed systems, we spent a lot of time working around this issue. This time, everything was just right.

Furthermore, our poor deployment environment (3 MacBooks…) made serious performance analysis very hard. Measuring the effect of scaling out is nearly impossible on a developer machine due to its physical resource limitations. Having multiple instances of the same services doesn’t give you anything if one of them already pushes the CPU to its limits. Luckily, there are almost infinite resources in the cloud nowadays which can be allocated in no time if required. It could be worth considering this option right away when working on microservice applications.

In Brief: Should you use Spring Cloud Netflix?

So what is our recommendation after all?

First, we were totally impressed by the way Eureka makes service discovery as easy as it can be. Given you are running Spring Boot, starting the Eureka server and making each microservice a Eureka client is nothing more than dependencies and annotations. On the other hand, we did not evaluate its integration in other environments.

Second, Hystrix is very useful for preventing cascading errors throughout the system, but it cannot be used in a production environment without suitable monitoring unless you have a soft spot for flying blind. Also, it introduces a few pitfalls during development. For example, when debugging a Hystrix command the calling code will probably detect a timeout in the meantime which can give you completely different behavior. However, if you got the tools and skills to handle the additional complexity, Hystrix is definitely a winner.

In fact, this restriction applies to microservice architectures in general. You have to go a long way for being able to run it – but once you are, you can scale almost infinitely. Feel free to have a look at the code we produced on github or discuss whatever you are up to at one of our user groups.

Advertisements

Introduction To The E-Commerce Backend commercetools platform

This blog post is an introduction to the e-commerce backend commercetools platform, which is a Platform as a Service (PaaS) of commercetools GmbH from Munich, and gives impulses on how to use it.

First the facts to commercetools and commercetools platform:

commercetools GmbH is a Munich based company situated in the north near Olympia Park and has further offices in Berlin and New York. The commercetools platform is a backend for all kinds of e-commerce use cases including online pure players, mobile and point-of-sales application, couch-commerce and marketplaces. commercetools began developing its platform in 2006 and has never stopped since.
I will at first give an overview of the UI of the platform with examples as to how to use it and then talk about the Rest API they provide in order to access data for an imaginary online shop.

User interface of commercetools platform

The sign up process is fairly easy and completed in about 5 minutes. You create an account and associate a project with it. One account can hold several projects and you can invite several accounts to one project. You will be asked whether you want to include test data in the project which is advisable for your first project.

Sphere Dashboard

Dashboard commercetools platform

The self-explanatory UI allows access to all needed functionalities from Products to Orders to Settings and content for developers. The first thing you will see is the dashboard which gives you revenue statistics for any given time.

I will guide you through the account as the workflow of creating a project should be:

  • Creating Product Types:
    At first you have to understand the difference between product types and categories. Product types are used to describe common characteristics and most importantly, common custom attributes, whereas categories are used to organize products in a hierarchical structure.

    creating a product type

    Creating a product type

    Look at the product type drink I created. I added two attributes, alcohol as a boolean and volume as a number. Now every product which is created using this product type has to have these two attributes additionally to all the other attributes I will show you later.

  • Creating Categories:
    As mentioned the categories are used to organize products in you project. This should be nothing spectacularly new.

    Creating categories

    Creating categories

    I decided to use a root category containing all other categories as subcategories to make my life easier later when retrieving the categories for the online shop. A category has just name, description, parents and children.

  • Creating Products:
    Now to the important part of the setup, the products itself. When creating a product you will have to choose one of the previously created product types. Note that a product can only be of one product type.

    Creating a product

    Creating a product

    After inserting name, description, the custom attributes and a few others the product is created. You can now upload pictures, add categories, create product variants (for example for different colors), add prices and even define SEO attributes.

  • Everything else via API:
    Creating Customers and Orders is possible in the UI but is, in my opinion, more practicable by API calls. This will be explained in the next part of this post.

REST API of commercetools platform

There are a lot of SDKs in different languages like Java, PHP and Node.JS for access to the API (check out the git-repository) but I decided to code directly against the API via the REST service. The API is fully documented here. I wrote an OnePage App with AngularJS and used the Angular $http service for my API calls, which I will show you in this part of my post. Transported data in both directions is in JSON format which allows fast and reliable handling.

Authorization

A client has to obtain an access token via an OAuth2 service. There are several access scopes, such as view_products, manage_orders and view_customers, which allow different kind of interaction with the platform. Normally you would have to implement a small server which handles the authentication and authorization. Otherwise the token would have to be stored on client side, which is not save, for with the manage_orders token a client can not only manage his own orders but all orders of the project. I ignored that for my test application and concentrated on the Rest API.

Getting Products

To obtain the products from the platform I used Angular’s http service:

function loadProducts(){
    $http.get('https://api.sphere.io/testshop-rw/product-projections?current=true')
        .success(function(data){$scope.loadProductsResponse = data;
                               handleLoadProductsResponse($scope.loadProductsResponse);
                                })
}

As response to this request you will receive a list of products with all parameters you can possibly need. Notable is the fast response time of the server which was never over 200 ms.

Carts, Customers and Orders

The most important task for an online shop is the handling of customers and their carts and orders. My test implementation creates an anonymous cart for every new user that enters the website:

if(localStorage['cartId'] === null){
    $http.post('https://api.sphere.io/testshop-rw/carts', {'currency':'EUR'/*,'customerId':localStorage['customerId']*/})
          .success(function(data){localStorage['cartId'] = data.id;})
}

As you can see I use the localStorage feature to store data. That way the customer can come back later or refresh the website without loosing previously obtained data. Once a customer logs in, the cart will be merged into the existing cart of the customer.

Registration for a customer is as simple as this:

function signUp(emailAddress, password, lastName, firstName, streetName, streetNumber, routingCode, city){
    $scope.registerCustomer = {
      email: emailAddress,
      firstName: firstName,
      lastName: lastName,
      password: password,
      anonymousCartId: localStorage['cartId'],
      addresses :[{
        email: emailAddress,
        firstName: firstName,
        lastName: lastName,
        streetName: streetName,
        streetNumber: streetNumber,
        postalCode: routingCode,
        city: city,
        country: 'DE'
     }]
  }
  angular.toJson($scope.registerCustomer)
  $http.post('https://api.sphere.io/testshop-rw/customers', $scope.registerCustomer)
    .success(function(data){$scope.signUpResponse = data;
                            signUpSuccess($scope.signUpResponse);
                            })
    .error(function(data){$scope.signUpResponse = data;
                          handleError(signUpResponse);
                          })
}

The customer can add several addresses including shipping and billing addresses which allows him to select one of them for checkout.

An order is created from a cart or an anonymous cart via POST:

function cartToOrder(updateCartResponse){
      $scope.makeOrder = {
        id : updateCartResponse.id,
        version : updateCartResponse.version
      }
      angular.toJson($scope.makeOrder);
      $http.post('https://api.sphere.io/testshop-rw/orders', $scope.makeOrder)
        .success(function(data){$scope.cartToOrderResponse = data;
                                orderSuccess($scope.cartToOrderResponse);})
}

The process a customer goes through until a product is ordered is fairly simple and only uses a few API calls.

Search

commercetools platform gives you built in fast search and filtering capabilities. Using NoSQL technology, the API allows you to create comprehensive product searches, after-search navigation and configuration. In addition, every change made to the product catalog is automatically indexed.
With the built-in facet technology you can enhance customer experience and usability with extended search and navigation capabilities. Therefore customers can find products faster – especially if you have a comprehensive and complex catalog.

The operators point of view

As the company which operates the online shop you have a pretty easy job, too. All products can be uploaded and updated via CSV files which allows you to manipulate all products at once and not one after the other. There are a few different payment statuses which can be given to orders with the payment state.

plug in integrations

plug in integrations

Orders can be downloaded in CSV or XML to feed them to your inventory control system and logistics provider.

Unfortunately as of yet there are no plug in payment methods which is sad but there is a silver lining. commercetools is working on that right now. The same with the direct integration of Hippo CMS which would allow you to manage all content via Hippo.
Other than that there are several ways to integrate the commercetools platform to your existing IT landscape (see graphic).

For more information on the commercetools platform, here are a few links which might be useful:

All in all I enjoyed working with commercetools because of the complete API documentation, the fast and very helpful support and the very fast and easy accessible API. Just sign up for a free trial and see for your self.

If you want to learn more about AngularJS, register now for our Training and get Early Bird Tickets.

Combining Logstash and Graylog for Log Management

A little incomplete overview

When working in a classic IT infrastructure you often face the problem that developers only have access to test or development environments, but not to production. In order to fix bugs or to have a glance at the system running in production, log file access is needed. This is often not possible due to security requirements. The result of this situation is that the operation guys need to provide these files to the developers, which can take a certain amount of time.

A solution to these problems is to provide a Log Management Server and grant access to the developers via a UI. Despite some commercial tools like Splunk, which is the de-facto market leader in this area, there are some quite promising open source solutions which do scale very well and may provide enough features to get the job done.

The advantage of using open source technology is that you can – but do not have to – buy subscriptions. Furthermore, software like Splunk and Log Analysis have pricing plans, which depend on the amount of logs you ship daily. The problem is that you have to pay more if the volume of logs increases either due to a raised log level to help analyze some bugs in production or simply as more services are deployed.

Last but not least, there are of course cloud solutions like Loggly. You can basically ship your log events to a cloud service, which then takes care of the rest. You do not have to provide any infrastructure yourself. This is a very good solution unless the security policy of your organization prohibits shipping data to the cloud.

Of course this overview is incomplete. I just picked some tools for a brief introduction. If you think something is missing, feel free to blog or comment about it.

Open Source Log Management

The famous ELK-Stack

At the moment, the probably most famous open source log management solution is the ELK-Stack. It is called a stack because it is not one software package but a combination of well-known open source tools. The components are:

  • Elasticsearch is a document oriented database optimized for searching. It is easily scalable and can manage a huge amount of data.
  • Logstash is a log forwarder with many features. There are many types of inputs, filters and outputs. Moreover logstash can handle a bunch of codecs, like JSON for example.
  • Finally, Kibana is the UI where you can view the log entries and create very sophisticated and colorful dashboards.

Despite all the good things about the ELK-Stack there are some drawbacks, which would make it not the optimal choice under some circumstances.

Kibana has no user management. If you want user management you have to purchase commercial support from Elastic to get a license for Shield.

Next, there is no housekeeping for the Elasticsearch database. Logstash creates an index for each day. You have to remove it manually if you do not need it anymore.

Graylog

Graylog is an alternative log management platform that addresses the drawbacks of the ELK stack and is quite mature. It provides an UI and a server part. Moreover, Graylog uses Elasticsearch as database for the log messages as well as MongoDB for application data.

The UI does basically what a UI does. It makes the data accessible in a web browser.

The server part provides a consistent management of the log files. The Graylog server has the following features:

  • Several inputs: HTTP, TCP, SYSLOG, AMQP, …
  • Classification for Log Messages (Streams)
  • User Management and Access Control for the defined streams
  • Simple Dashboards created from streams
  • Housekeeping in Elasticsearch
  • Outputs to forward the messages of a particular stream

Moreover, Graylog can easily be deployed in a clustered environment, so that you get high availability and load distribution.

In order to create a full solution it is suitable to combine Graylog with Logstash with a little patching of Logstash and a custom Graylog Plugin.

As a standard for log events, Graylog promotes usage of the Graylog Extended Log Format (GELF). This is basically a JSON format containing the following information:

  • Timestamp (Unix): time of log event
  • Host: host where the event originates
  • short_message: message

A GELF message can contain many other optional fields as well as user-defined fields. The timestamp is really important to see the log messages ordered by log message creation time and not at the time when entering the system.

Putting it all together

Unfortunately it’s a little bit challenging to make Logstash talk to Graylog and vice versa. The main problem is that Graylog wants the end of a message with a NULL delimiter whereas Logstash creates \n. Logstash also expects \n when receiving log messages as well as Graylog sends log messages with the NULL delimiter.

Sending messages from Logstash to Graylog

1. Use a message broker like RabbitMQ. Logstash can write to RabbitMQ, Graylog can read. This solution decouples both applications, so that the Graylog server can be shut down while Logstash is still producing log messages.

2. Use the HTTP input in Graylog to receive messages from Logstash. This solution has some drawbacks. The biggest might be that if Graylog is down, Logstash discards the message after a failed send attempt.

3. Use the GELF TCP input and patch Logstash. Unfortunately, there is no possibility to change the line separator in the Logstash “json_lines” codec. This could be done in a patch which is currently open as a pull request. Hopefully, it will be merged soon. The big advantage in using the Logstash TCP output is that Logstash queues messages which cannot be send and retries sending them.

Sending messages from Graylog to Logstash

Sending messages from Graylog to Logstash might not make sense in the first place. But if you think of creating a file-based archive of log files on a NAS or in AWS S3 it might make sense though.

As mentioned above, even there is a problem with the line ending. Fortunately, Graylog provides a plugin API. So I created a plugin which can forward log messages to a Logstash instance. This instance can write the log files then.

The plugin is hosted on Github and licensed under the APL 2.0.

Conclusion

As described in the article, you can combine Logstash and Graylog with little effort in order to build an enterprise-ready flexible, scalable and access controlled log management system. Graylog and Elasticsearch as central components are able to scale out the described setup and can handle a huge load of data.

Graylog, Logstash and Elasticsearch are all three high-quality open source tools with a great community and many users. All these products are also commercially supported by companies behind them.

Finally there is one important note for all the Kibana lovers. Of course it is possible to also deploy Kibana in parallel to Graylog. Then you can build nice dashboards with Kibana and have the features like User Management and Elasticsearch Housekeeping in Graylog.

Graylog

Cross Language Benchmarking Part 3 – Git submodules and the single-command cross language benchmark

In my recent blog posts (part 1, part 2) I have described in detail how to do micro benchmarking for Java and C/C++ with JMH and Hayai. I have presented a common execution approach based on Gradle.

Today I want to improve the overall project structure. Last time I already mentioned, that the project structure of the Gradle projects is not optimal. In the first part I will roughly repeat the main goal and proceedings from the past articles, secondly introduce some new requirements, and finally I will present you a more flexible module structure to split production code and benchmarks, which will then be embedded in a cross language super-project.
Continue reading

Cross-language benchmarking made easy?

There is this eternal fight between the different programming languages. “The code in ‘XYZlang’ runs much faster than in ‘ABClang'”. Well, this statement bears at least three misunderstandings. First of all, in most cases it is not the source code you wrote, that gets actually executed, second thing, please define “faster” upfront, third thing, and general rule of experiments: Do not draw conclusions based on a benchmark, see them more of a hint that some things seem to make a difference.

In this article series, I will not discuss about numbers and reasons, why code X or language Y supersedes the other. There are many people out there who understand the backgrounds much better than me – here, short hint to a very good article about Java vs. Scala benchmarking by Aleksey Shipilëv. There will be no machine code, no performance tweaks which make your code perform 100 times better.  I want to present you ideas, how you can set such micro benchmarks up in a simple, automated and user friendly way. In detail we will come across these topics:

  • How to setup one build that fits all requirements?
  • Gradle in action building across different languages
  • Benchmarking with JMH (java) and Hayai (C/C++) to proof the concept
  • How to store the results?

Continue reading

Teamgeist on Android Wear

Die ganze IT Welt spricht derzeit von Wearables. Also wollte ich mir in einem Lab die Android Wear API genauer anschauen. Schnell war auch schon der erste Anwendungsfall gefunden. In unserer Teamgeist App gibt es seit kurzem die Möglichkeit Kudos zu verteilen.

Kudos

Auf einer Android Wear Uhr würden sich die Kudos prima darstellen lassen. Dazu gäbe es zwei Aktionen. Einmal für einen Kudo “voten”. Die andere wäre die Teamgeist App öffnen.

Für eine Integration mit der Teamgeist App bräuchten wir eine neue Schnittstelle. Zum kennen lernen der Android Wear Api begnügen wir uns deswegen im folgenden mit einer Android App die Kudos erstellt und verschickt.

Nach kurzer Recherche wurde klar, dass für den Anwendungsfall gar keine eigene Android Wear App notwendig ist. Es reicht eine normale Android App die mittels der Notifications API Nachrichten direkt an die Uhr versendet. Anwendungen eigens für Android Wear geschrieben, werden in einem späteren Tutorial näher beleuchtet.

Vorbereitung

Ein paar Dinge die wir benötigen bevor wir loslegen können:

  • Intellij (14) als IDE
  • Android SDK mit installierten API Packages für Level 19 (4.4.2), 20 (4.4W) und Android Support Library V4 (20)

Android SDK

  • Aus Mangel einer echten Android Wear starten wir eine aus dem AVD Manager heraus

AVD Wear

Für das Koppeln mit einem Handy benötigen wir auf dem Handy die Android Wear App aus dem Play Store. Das koppeln von der emulierten Wear und einem per USB angeschlossen Handy funktioniert erst dann wenn folgender Befehl auf Kommandozeile eingegebenen wurde (im Tools Verzeichnis vom android-sdk):

~/development/android-sdk-mac_86/platform-tools$ adb -d forward tcp:5601 tcp:5601

Erst wenn der Befehl ohne Fehler ausgeführt wurde, lässt sich aus der Android Wear App im Handy die emulierte Uhr mit dem Handy verbinden. Wird das Handy vom Rechner getrennt und neu angeschlossen, muss der Befehl erneut ausgeführt werden. Eine genau Beschreibung gibt es von Google oder hier.

Neue Android App erstellen

Nachdem wir den Emulator mit dem Handy erfolgreich gekoppelt haben, erscheinen auch schon die ersten Notifications auf der Uhr wie z.B. der Eingang neuer Mails.

Damit wir nun selbst Notifications versenden können erstellen wir InteliJ ein neues Projekt. Im ersten Bildschirm wählen wir links Android und rechts das Gradle: Android Module aus. Auf der darauffolgenden Seite müssen wir ein paar Einstellungen wie z.b. die Version des Target SDK vornehmen.

Target SDK

Anmerkung: Wir hätten hier auch 4.3 wählen können da die Android Wear App ab Android 4.3 unterstützt wird.

Auf den nächsten Seiten belassen wir die Einstellung wie sie sind und wählen auf dem letzten Bildschirm nur noch den Ordner für unser Projekt aus.

Cleanup des generierten Projektes

In unserer Teamgeist App benötigen wir natürlich als erstes unseren Teamgeist und fügen diesen zu den drawables hinzu 🙂

teamgeist_logo

 

In der activity_main.xml löschen wir die TextView und erstellen statt dessen einen Button.

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Sende Kudos"
    android:id="@+id/kudo_button" android:layout_centerVertical="true" android:layout_centerHorizontal="true"/>

Um mit den Button in Java zu arbeiten holen wir uns eine Referenz darauf in der MainActivity#onCreate() Methode und setzen auch gleich einen OnClickListener.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button kudoButton = (Button)findViewById(R.id.kudo_button);
    kudoButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
          //hierher kommt unser Notification Code
        }
    });
}

Wenn wir jetzt unsere App starten, sollte sich auf dem Handy die App öffnen mit einem Button “Sende Kudos” auf weißem Hintergrund.

Senden einer ersten Notification

Um eine erste Notification zu senden müssen wir noch die V4 Support Library zu unserem Projekt hinzufügen. Hierzu fügen wir der dependency Section unserer build.gradle Datei eine Zeile hinzu.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "com.android.support:support-v4:20.0.+"
}

Beim ersten mal hinzufügen der V4 Support Library zu einem Projekt erkennt IntelliJ dies und erstellt durch nachfragen ein eigenes Repository hierfür.

Nun können wir auf die Notification API in der onClick Methode des vorher erstellten OnClickListeners zugreifen und fügen folgenden Code hinzu.

@Override
public void onClick(View view) {
  //1. Erstellen eines NotificationCompat.Builder mit Hilfe des Builder Patterns
  Notification notification =
    new NotificationCompat.Builder(MainActivity.this)
      .setSmallIcon(R.drawable.teamgeist_logo)
      .setContentTitle("Notifications?")
      .setContentText("Congratulations, you have sent your first notification")
      .build();

  //2. Wir benötigen einen NotificationManager
  NotificationManagerCompat notificationManager =
    NotificationManagerCompat.from(MainActivity.this);

  //3. Versenden der Notification mittels NotificationManager und NotificationBuilder
  int notificationId = 1;
  notificationManager.notify(notificationId, notification);

}
  1. Als erstes wird mit Hilfe des NotificationCompat.Builder und dem Builder Pattern eine Notification erstellt. Hier setzen wir zu Beginn einen Titel, einen Text und ein Bild.
  2. Dann benötigen wir zum versenden einen NotificationManager. Den erhalten wir mit dem Aufruf der from() Methode von der Klasse NotificationManagerCompat.
  3. Danach sind wir bereit die Notification über die notify Methode des NotificationManagers zu verschicken. Die notificationId dient hierbei zur Unterscheidung von verschiedenen Notifications einer App.

Wenn wir die App jetzt deployen, starten und auf “Kudo senden” drücken kriegen wir unsere erste eigene Notification auf der Uhr.

simple_notification

Hintergrundbild

Anhand des App Icons ermittelt Android eine ähnliche Hintergrundfarbe. Ein eigenes Bild sieht jedoch viel besser aus. Wir erreichen dies in dem wir beim Builder zusätzlich setLargeIcon aufrufen.

new NotificationCompat.Builder(MainActivity.this)
 .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.teamgeist_logo))
 .setSmallIcon(R.drawable.teamgeist_logo)
 .setContentTitle("Notifications?")
 .setContentText("Congratulations, you have sent your first notification")
 .build();

Damit kriegt die Notification auf der Uhr den Geist auch als Hintergrund.

simple_notification_with_background

Benutzerinteraktion

Wir können der Notification verschiedene Benutzerinteraktionen hinzufügen. Mit einem PendingIndent wird beispielsweise eine bestimmte Activity in unserer App aufgerufen und ihr mittels “Extras” Daten übergeben. Den PendingIndent erstellen wir in einer eigenen Methode.

private PendingIntent createContentIntent() {
    Intent viewIntent = new Intent(MainActivity.this, MainActivity.class);
    viewIntent.putExtra("EventNotified", "1");
    PendingIntent viewPendingIntent =
          PendingIntent.getActivity(MainActivity.this, 0, viewIntent, 0);
    return viewPendingIntent;
}

Diesen Indent übergeben wir dem Builder durch Aufruf von setContentIntent.

new NotificationCompat.Builder(MainActivity.this)
 .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.teamgeist_logo))
 .setSmallIcon(R.drawable.teamgeist_logo)
 .setContentTitle("Notifications?")
 .setContentText("Congratulations, you have sent your first notification")
 .setContentIntent(createContentIntent())
 .build();

Durch nach links Wischen der Notification erscheint unsere neue Aktion.

PendingIntent

Klicken wir nun auf “Open on phone” öffnet sich die hinterlegte Activity im Handy, also in unserem Fall die MainActivity. Leider bleibt bisher die Notification auf der Uhr bestehen. Um sie dort zu entfernen, müssen wir abfragen ob die App durch die User Interaktion gestartet wurde und deaktivieren in diesem Falle die Notification. Dazu erstellen wir uns die Methode cancelNotificationOnUserInteraction Methode und rufen sie in der MainActivity#onCreate Methode auf.

private void cancelNotificationOnUserInteraction() {
    Intent intent = getIntent();
    Bundle extras = intent.getExtras();
    if (extras != null && "1".equals(extras.getString("EventNotified"))) {
        NotificationManagerCompat.from(this).cancel(1);
    }
}

Neben dieser Standard Aktion können wir weitere “Actions” hinzufügen. Dazu erstellen wir uns ein Action Objekt mit folgender Methode,

private NotificationCompat.Action showInBrowser() {
    Intent browserIntent = new Intent(Intent.ACTION_VIEW);
    Uri geoUri = Uri.parse("http://app.teamgeist.io");
    browserIntent.setData(geoUri);
    PendingIntent browserPendingIntent =
            PendingIntent.getActivity(this, 0, browserIntent, 0);

    return new NotificationCompat.Action(
            android.R.drawable.ic_dialog_map, "Open in Browser", browserPendingIntent);
}

und übergeben das Objekt an den Builder mittels der addAction Methode.

new NotificationCompat.Builder(MainActivity.this)
 .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.teamgeist_logo))
 .setSmallIcon(R.drawable.teamgeist_logo)
 .setContentTitle("Notifications?")
 .setContentText("Congratulations, you have sent your first notification")
 .setContentIntent(createContentIntent())
 .addAction(showInBrowser())
 .build();

Wir können die Notification jetzt zweimal nach links schieben und kriegen dann eine weitere Aktion zur Auswahl. Beim klicken auf “Open in Browser” öffnet sich nun unsere Teamgeist Webseite auf dem Handy.

OpenInBrowserAction

Mit Hilfe so einer Action würden wir die Voting Funktion realisieren. Die App auf dem Handy müsste dann dem Teamgeist Server den vote übermitteln.

Was gibt es noch?

Damit sind wir am Ende unseres ersten Android Wear Labs angekommen. Neben diesen Aktionen gibt es noch besondere Wear Notification Features. Da wäre zum einen die Möglichkeit die Notification um mehr als eine “Page” zu erweitern. Oder Notifications zu gruppieren. Jedoch das wahrscheinlich bekannteste Feature ist die Möglichkeit auf eine Notification mittels Sprache zu antworten.

All dies sind potentielle Themen für unser nächstes Android Lab. Und natürlich möchten wir die App mit unserem Teamgeist Server verbinden um echte Kudos zu erhalten und für sie “voten” ;-).

Spring-Shell – an easy way to create your own shell

If your next mind blowing tool needs to get some user interaction, using command line arguments might not always be the best user experience. So if you want to provide a more convenient way for the user to interact with your program, then a shell can be a solution. Using a shell gives you the power to lead the user in the process of interaction by predefining commands and thereby giving a hint on what is possible. With Spring-Shell, it is pretty easy to create a shell with your own commands that gives you access to the functions of your program.

How it Works

The shell is based on the Spring-Framework and already provides default built in commands for basic functions like exiting the shell, getting a help page or even using unix/windows commands. It also has some converters for reading in different types of input, like boolean or date. Besides that it contains a plugin model which can be used to customize the shell. Therefore to use your own commands, you need to write a plugin, that will be read in by the plugin model. Each plugin has to contain the file Meta-Inf/spring/spring-shell-plugin.xml. In this file you have to declare where to find the classes that define your custom commands, e.g. with the spring component-scanning functionality.

Continue reading