Skip to content
This repository was archived by the owner on Jul 18, 2024. It is now read-only.
86 changes: 73 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/IBM/office-space.svg?branch=master)](https://travis-ci.org/IBM/office-space)
# OfficeSpace: Polyglot Microservices application leveraging Java Spring Boot on Kubernetes

Spring Boot is one of the popular Java microservices framework. Spring Cloud has a rich set of well integrated Java libraries to address runtime concerns as part of the Java application stack, and Kubernetes provides a rich featureset to run polyglot microservices. Together these technologies complement each other and make a great platform for Spring Boot applications.
Spring Boot is one of the popular Java microservices framework. Spring Cloud has a rich set of well integrated Java libraries to address runtime concerns as part of the Java application stack, and Kubernetes provides a rich featureset to run polyglot microservices. Together these technologies complement each other and make a great platform for Spring Boot applications.

In this code we demonstrate how a simple Spring Boot application can be deployed on top of Kuberneets. This application, Office Space, mimicks the fictitious app idea from Michael Bolton in the movie [Office Space](http://www.imdb.com/title/tt0151804/). The app takes advantage of a financial program that computes interest for transactions by diverting fractions of a cent that are usually rounded off into a seperate bank account.

Expand Down Expand Up @@ -31,10 +31,13 @@ Please follow the [Toolchain instructions](https://github.com/IBM/container-jour
4. [Create the Transaction Generator service](#4-create-the-transaction-generator-service)
5. [Access Your Application](#5-access-your-application)

#### [Using OpenWhisk](#using-openwhisk-action-with-slack-notification)
#### [Troubleshooting](#troubleshooting-1)

# 1. Create the Database service
The backend consists of the MySQL database and the Spring Boot app. You will also be creating a deployment controller for each to provision their Pods.

* There are two ways to create the MySQL database backend: **Use MySQL in a container in your cluster** *OR* **Use Bluemix MySQL**
* There are two ways to create the MySQL database backend: **Use MySQL in a container in your cluster** *OR* **Use Bluemix MySQL**

## 1.1 Use MySQL in container
**NOTE:** Leave the environment variables blank in the `compute-interest-api.yaml` and `account-summary.yaml`
Expand Down Expand Up @@ -72,7 +75,11 @@ deployment "account-database" created
You will need to have [Maven installed on your environment](https://maven.apache.org/index.html).
If you want to modify the Spring Boot apps, you will need to do it before building the Java project and the docker image.

The Spring Boot Microservices are the **Compute-Interest-API** and the **Email-Service**.
The Spring Boot Microservices are the **Compute-Interest-API** and the **Send-Notification**.

The **Send-Notification** can be configured to send notification through gmail and/or Slack.

You will need to use an [OpenWhisk](#using-openwhisk-action-with-slack-notification) action for the slack notification. You may also choose to use an OpenWhisk action for the email notification. If you want to use OpenWhisk, go to [Using OpenWhisk](#using-openwhisk-action-with-slack-notification) section before building and deploying the images. Otherwise, you can proceed if you choose to only have an email notification setup.

* 1. Build the images

Expand All @@ -84,7 +91,7 @@ The Spring Boot Microservices are the **Compute-Interest-API** and the **Email-S

Go to containers/email-office-space
$ mvn package
$ docker build -t registry.ng.bluemix.net/<namespace>/email-service .
$ docker build -t registry.ng.bluemix.net/<namespace>/send-notification .
```
*We will be using Bluemix container registry to push images (hence the image naming), but the images [can be pushed in Docker hub](https://docs.docker.com/datacenter/dtr/2.2/guides/user/manage-images/pull-and-push-images) as well.*
* 2. Push the images:
Expand All @@ -96,23 +103,23 @@ The Spring Boot Microservices are the **Compute-Interest-API** and the **Email-S

```bash
$ docker push registry.ng.bluemix.net/<namespace>/compute-interest-api
$ docker push registry.ng.bluemix.net/<namespace>/email-service
$ docker push registry.ng.bluemix.net/<namespace>/send-notification
```
* 3. Modify `compute-interest-api.yaml` and `email-service.yaml` to use your image
* 3. Modify `compute-interest-api.yaml` and `send-notification.yaml` to use your image
```yaml
// compute-interest-api.yaml
spec:
containers:
- image: registry.ng.bluemix.net/<namespace>/compute-interest-api # replace with your image name
// email-service.yaml
// send-notification.yaml
spec:
containers:
- image: registry.ng.bluemix.net/<namespace>/email-service # replace with your image name
- image: registry.ng.bluemix.net/<namespace>/send-notification # replace with your image name
```
You will also need to modify the **environment variables** in the `email-service.yaml`:
You will also need to modify the **environment variables** in the `send-notification.yaml`:
```yaml
env:
- name: GMAIL_SENDER_USER
- name: GMAIL_SENDER_USER
value: 'username@gmail.com' # change this to the gmail that will send the email
- name: GMAIL_SENDER_PASSWORD
value: 'password' # change this to the the password of the gmail above
Expand All @@ -126,9 +133,9 @@ The Spring Boot Microservices are the **Compute-Interest-API** and the **Email-S
deployment "compute-interest-api" created
```
```bash
$ kubectl create -f email-service.yaml
service "email-service" created
deployment "email-service" created
$ kubectl create -f send-notification.yaml
service "send-notification" created
deployment "send-notification" created
```
> Note: The compute-interest-api multiplies the fraction of the pennies to x100,000 for simulation purposes. You can edit/remove the line `remainingInterest *= 100000` in `src/main/java/officespace/controller/MainController.java` then build the image again.

Expand Down Expand Up @@ -178,6 +185,59 @@ account-summary 10.10.10.74 <nodes> 80:30080/TCP
* On your browser, go to `http://<your-cluster-IP>:30080`
![Account-balance](images/balance.png)

# Using OpenWhisk Action with Slack Notification

Requirements for this sections:
* [Slack Incoming Webhook](https://api.slack.com/incoming-webhooks) in your Slack team.
* **Bluemix Account** to use [OpenWhisk](https://console.ng.bluemix.net/openwhisk/).


1. Create an OpenWhisk Action
* Click on [Developer in your Browser](https://console.ng.bluemix.net/openwhisk/) and click on **Create an Action**
![Create-Action](images/developBrowser.png)

* Then click on
![Create-Action](images/createAction.png)

![Create-Action](images/action.png)
* Copy the [sendSlack.js](/sendSlack.js) for sending a Slack Notification then save it
![Copy-Script](images/copyScript.png)
* Set your [Slack Webhook URL](https://api.slack.com/incoming-webhooks) as default parameter for the action then save it
Click on View Action Details
![Set-Default](images/viewAction.png)
Then set `url` to `https://< Your Slack Team's incoming webhook url>`
![Set-Default](images/defaultParameters.png)
* Create another action for [sendEmail.js](/sendEmail.js) for sending an email through Gmail.

2. Create Managed API
* From the API tab, Create Managed API
![Managed-API](images/createManaged.png)

* Then set an API name
![Managed-API](images/api.png)
* Create an operation. Make it a **POST request** and **select the Slack Action** you just created. **Do the same fore the Email Action**.
![Create-Operation](images/createOperation.png)

![Create-Operation](images/operation.png)
* Go to the API Explorer section on your managed API and take note of the URL for both **Slack** and **Email** operations.
![API-Url](images/apiUrl.png)
3. Modify `send-notification.yaml`
* Fill in the necessary values on the environment variables
```yaml
- name: OPENWHISK_API_URL_SLACK
value: 'openwhisk api url for slack action' # enter the url of the API you just created
- name: SLACK_MESSAGE
value: 'Your balance is over $50,000.00' # set the slack message
- name: OPENWHISK_API_URL_EMAIL
value: 'openwhisk api url for email action'
```
4. Redeploy your Application


## Troubleshooting
* To start over, delete everything: `kubectl delete svc,deploy -l app=office-space`


## References
* [John Zaccone](https://github.com/jzaccone) - The original author of the [office space app deployed via Docker](https://github.com/jzaccone/office-space-dockercon2017).
* The Office Space app is based on the 1999 film that used that concept.
Expand Down
2 changes: 1 addition & 1 deletion compute-interest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ spec:
tier: compute
spec:
containers:
- image: anthonyamanse/compute-interest-api-with-email
- image: anthonyamanse/compute-interest-api:4.0
imagePullPolicy: Always
name: compute-interest-api
env:
Expand Down
Binary file modified containers/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public String computeInterest(@RequestBody(required = true) Transaction transact
if (updatedBalance > 50000 && emailSent == false ) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
String server = "http://email-service:8080/email";
String server = "http://send-notification:8080/email";
headers.add("Content-Type", "application/json");
headers.add("Accept", "*/*");
String json = "{}";
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.example.controller;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -21,33 +20,72 @@ public class TriggerEmail {

@Autowired
private JavaMailSender mailSender;

@Value("${spring.mail.username}")
private String sender;

@Value("${trigger.mail.receiver}")
private String receiver;


@Value("${trigger.slack.url}")
private String slack_url;

@Value("${trigger.slack.message}")
private String slack_message;

@Value("${trigger.email.url}")
private String email_url;

@Value("${spring.mail.password}")
private String password;

@RequestMapping(path = "/email", method = RequestMethod.POST)
private String send() {
MimeMessage mail = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mail);

try {
helper.setTo(receiver);
helper.setFrom(sender);
helper.setReplyTo(sender);
helper.setSubject("Office-Space Notification");
helper.setText("Account Balance is now over $50,000");
mailSender.send(mail);
if (email_url.isEmpty()) {
helper.setTo(receiver);
helper.setFrom(sender);
helper.setReplyTo(sender);
helper.setSubject("Office-Space Notification");
helper.setText("Account Balance is now over $50,000");
mailSender.send(mail);
}
else {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
String server = email_url;
headers.add("Content-Type", "application/json");
headers.add("Accept", "*/*");
String json = "{\"text\": \"" + slack_message + "\",\"sender\": \"" + sender + "\",\"receiver\": \"" + receiver + "\",\"password\": \"" + password + "\",\"subject\": \"Office-Space Notification\"}";

HttpEntity<String> requestEntity = new HttpEntity<String>(json, headers);
ResponseEntity<String> responseEntity = rest.exchange(server, HttpMethod.POST, requestEntity, String.class);
}


if (!slack_url.isEmpty()) {
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
String server = slack_url;
headers.add("Content-Type", "application/json");
headers.add("Accept", "*/*");
String json = "{\"text\": \"" + slack_message + "\"}";

HttpEntity<String> requestEntity = new HttpEntity<String>(json, headers);
ResponseEntity<String> responseEntity = rest.exchange(server, HttpMethod.POST, requestEntity, String.class);
}

return "{\"message\": \"OK\"}";
} catch (MessagingException e) {
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "{\"message\": \"Error\"}";
}
}

@RequestMapping(path = "/triggertest", method = RequestMethod.POST)
private String trigger() {
RestTemplate rest = new RestTemplate();
Expand All @@ -56,7 +94,7 @@ private String trigger() {
headers.add("Content-Type", "application/json");
headers.add("Accept", "*/*");
String json = "{}";

HttpEntity<String> requestEntity = new HttpEntity<String>(json, headers);
ResponseEntity<String> responseEntity = rest.exchange(server, HttpMethod.POST, requestEntity, String.class);
return responseEntity.getBody();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ spring.mail.properties.mail.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.socketFactory.fallback=false
spring.mail.smtp.port=587

trigger.mail.receiver=${EMAIL_RECEIVER}
trigger.mail.receiver=${EMAIL_RECEIVER}
trigger.slack.url=${OPENWHISK_API_URL_SLACK}
trigger.slack.message=${SLACK_MESSAGE}
trigger.email.url=${OPENWHISK_API_URL_EMAIL}
Binary file added images/action.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/api.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/apiUrl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/copyScript.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/createAction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/createManaged.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/createOperation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/defaultParameters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/developBrowser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/operation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/viewAction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 12 additions & 6 deletions email-service.yaml → send-notification.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: email-service
name: send-notification
labels:
app: office-space
spec:
Expand All @@ -11,12 +11,12 @@ spec:
targetPort: 8080
selector:
app: office-space
tier: email
tier: notification
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: email-service
name: send-notification
labels:
app: office-space
spec:
Expand All @@ -26,18 +26,24 @@ spec:
metadata:
labels:
app: office-space
tier: email
tier: notification
spec:
containers:
- image: anthonyamanse/email-service
- image: anthonyamanse/send-notification:1.0
imagePullPolicy: Always
name: email-service
name: send-notification
env:
- name: GMAIL_SENDER_USER
value: 'username@gmail.com'
- name: GMAIL_SENDER_PASSWORD
value: 'password'
- name: EMAIL_RECEIVER
value: 'sendTo@gmail.com'
- name: OPENWHISK_API_URL_SLACK
value: 'openwhisk api url for slack action'
- name: SLACK_MESSAGE
value: 'Your balance is over $50,000.00'
- name: OPENWHISK_API_URL_EMAIL
value: 'openwhisk api url for email action'
ports:
- containerPort: 8080
43 changes: 43 additions & 0 deletions sendEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';
var nodemailer = require('nodemailer');

function main(params) {



// create transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: params.sender,
pass: params.password
}
});
console.log('SMTP Configured??');

// setup email data
let mailOptions = {
from: '"' + params.sender + '" <' + params.sender + '>', // sender address
to: params.receiver, // list of receivers
subject: params.subject, // Subject line
text: params.text, // plain text body
};
console.log("Mail configured");


//send mail
var promise = new Promise(function (resolve, reject) {
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log(error);
reject(error);
}
else {
console.log('Message %s sent: %s', info.messageId, info.response);
resolve(info);
}
});
});
return promise;

}
Loading