Spring Data - powerful and succinct abstraction

Database tier definition

Database tables, indexes and foreign keys defined in Liquibase configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
databaseChangeLog:
- changeSet:
id: 1
author: Terrence Miao
changes:
- createTable:
tableName: draft_order
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: c_number
type: varchar(32)
constraints:
nullable: false
- column:
name: source_time_in_ms
type: bigint
constraints:
nullable: false
- column:
name: source_item_id
type: varchar(255)
constraints:
nullable: false
- column:
name: shipment
type: json
constraints:
nullable: false
- column:
name: shipment_id
type: varchar(255)
constraints:
nullable: true
- column:
name: quantity
type: int
constraints:
nullable: false
- column:
name: source_system
type: varchar(255)
constraints:
nullable: false
- column:
name: status
type: varchar(32)
constraints:
nullable: false
- createIndex:
columns:
- column:
name: source_item_id
indexName: idx_source_item_id
tableName: draft_order
unique: false
- createIndex:
columns:
- column:
name: c_number
- column:
name: source_item_id
indexName: idx_c_number_source_item_id
tableName: draft_order
unique: true
- createTable:
tableName: draft_order_combined
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: combined_id
type: varchar(64)
constraints:
nullable: false
- column:
name: draft_order_id
type: int
constraints:
nullable: false
- addForeignKeyConstraint:
baseColumnNames: draft_order_id
baseTableName: draft_order_combined
constraintName: fk_draft_order_combined_draft_order
onDelete: CASCADE
onUpdate: RESTRICT
referencedColumnNames: id
referencedTableName: draft_order
- changeSet:
id: 2
author: Terrence Miao
changes:
- addColumn:
columns:
- column:
# For MySQL 5.7.x above, the first TIMESTAMP column in the table gets current timestamp as the default value, likely. So
# if an INSERT or UPDATE without supplying a value, the column will get the current timestamp. Any subsequent TIMESTAMP
# columns should have a default value explicitly defined. If you have two TIMESTAMP columns and if you don't specify a
# default value for the second column, you will get this error while trying to create the table:
# ERROR 1067 (42000): Invalid default value for 'COLUMN_NAME'
name: date_created
type: timestamp(3)
constraints:
nullable: false
- column:
name: date_updated
type: timestamp(3)
defaultValueComputed: LOCALTIMESTAMP(3)
constraints:
nullable: false
tableName: draft_order

DAO definition

  • Draft Order
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Entity
@Table(name = "draft_order")
public class DraftOrder implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "c_number")
private String cNumber;

@Column(name = "source_time_in_ms")
private Long sourceTimeInMs;

@Column(name = "source_item_id")
private String sourceItemId;

@Column(name = "shipment", columnDefinition = "json")
private String shipment;

@Column(name = "shipment_id")
private String shipmentId;

@Column(name = "quantity")
private Integer quantity;

@Column(name = "source_system")
private String sourceSystem;

@Column(name = "status")
private String status;
}
  • Draft Order Combined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
@Table(name = "draft_order_combined")
public class DraftOrderCombined implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "combined_id")
private String combinedId;

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "draft_order_id")
private DraftOrder draftOrder;
}
  • An middle Aggregation Object
1
2
3
4
5
6
7
8
9
10
11
12
public class CombinedIdSourceTimeInMs {

private Long counter;
private String combinedId;
private Long sourceTimeInMs;

public CombinedIdSourceTimeInMs(Long counter, String combinedId, Long sourceTimeInMs) {
this.counter = counter;
this.combinedId = combinedId;
this.sourceTimeInMs = sourceTimeInMs;
}
}

CRUD Repository definition

  • DraftOrderRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface DraftOrderRepository extends CrudRepository<DraftOrder, Integer> {

List<DraftOrder> findByCNumberAndStatusOrderBySourceTimeInMsDesc(String cNumber, String status, Pageable pageable);

List<DraftOrder> findByCNumberAndSourceItemIdIn(String cNumber, List<String> sourceItemIds);

DraftOrder findByCNumberAndSourceItemId(String cNumber, String sourceItemId);

List<DraftOrder> findByShipmentIdInAndStatusAndSourceSystem(List<String> shipmentIds, String status, String sourceSystem);

List<DraftOrder> findByCNumberAndId(String cNumber, Integer id);

Long countByCNumberAndStatus(String cNumber, String status);
}
  • DraftOrderCombinedRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface DraftOrderCombinedRepository extends CrudRepository<DraftOrderCombined, Integer> {

String FIND_QUERY =
"SELECT new org.paradise.data.dao.CombinedIdSourceTimeInMs"
+ "(count(doc) as counter, doc.combinedId as combinedId, min(doc.draftOrder.sourceTimeInMs) as sourceTimeInMs) "
+ " FROM DraftOrderCombined doc WHERE doc.draftOrder.cNumber = :cNumber AND doc.draftOrder.status = :status "
+ " GROUP BY combinedId "
+ " ORDER BY sourceTimeInMs DESC";

String COUNT_QUERY = "SELECT count(1) FROM "
+ "(SELECT count(1) FROM DraftOrderCombined doc WHERE doc.draftOrder.cNumber = :cNumber AND doc.draftOrder.status = :status"
+ " GROUP BY doc.combinedId)";

@Query(value = FIND_QUERY, countQuery = COUNT_QUERY)
List<CombinedIdSourceTimeInMs> countPerCombinedIdAndSourceTimeInMs(@Param("cNumber") String cNumber,
@Param("status") String status, Pageable pageable);

List<DraftOrderCombined> findByCombinedIdOrderByDraftOrderDaoSourceTimeInMsDesc(String combinedId);
}

References

SQL script generates random data and insert into MySQL database

1
DROP PROCEDURE InsertRandomRecords;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DELIMITER $$
CREATE PROCEDURE InsertRandomRecords(IN NumRows INT)
BEGIN
DECLARE i INT;
SET i = 1;
START TRANSACTION;
WHILE i <= NumRows DO
INSERT INTO draftorders.draft_order (c_number, source_time_in_ms, source_item_id, shipment, shipment_id, quantity, source_system, status)
VALUES ('C01234567890', RAND()*1000000000, CONCAT('randomSourceRef-', UUID_SHORT()),
'{"to": {"name": "T T", "lines": ["Lvl 100", "123 smith st"], "phone": "0356567567", "state": "VIC", "suburb": "Greensborough", "postcode": "3088", "business_name": "In debt"}, "from": {"name": "Carl Block", "lines": ["1341 Dandenong Road"], "state": "VIC", "suburb": "Geelong", "postcode": "3220"}, "items": [{"width": "10", "height": "10", "length": "10", "weight": "10", "product_id": "3D85", "item_reference": "blocked", "authority_to_leave": true, "allow_partial_delivery": true, "contains_dangerous_goods": true}], "shipment_reference": "My second shipment ref", "customer_reference_1": "cr1234", "customer_reference_2": "cr5678"}',
UUID(), 1, 'EBAY', ELT(1 + FLOOR(RAND()*3), 'DRAFT', 'READY_TO_SHIP', 'SHIPPED'));
SET i = i + 1;
END WHILE;
COMMIT;
END$$
DELIMITER ;

To generate 1,000,000 draft orders:

1
CALL InsertRandomRecords(1000000);

Set up and run AWS Lambda 'hello' function with serverless

serverless

With latest Node.js 6.x.x installed, then install serverless globally:

1
$ npm install serverless -g

AWS Lambda

Create a AWS Lambda skeleton project with serverless:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mkdir serverless-example && cd $_

$ sls create -t aws-nodejs
Serverless: Generating boilerplate...
_______ __
| _ .-----.----.--.--.-----.----| .-----.-----.-----.
| |___| -__| _| | | -__| _| | -__|__ --|__ --|
|____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
| | | The Serverless Application Framework
| | serverless.com, v1.7.0
-------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name
  • Policies set up for Lambda function

For AWS user “ec2-user”, now need to have some policies with permissions to let “serverless” create role, Lambda function and deployment it …

Polices set up for Lambda function

  • Roles for Lambda function

Lambda function role created after Lambda function added and deployed into AWS.

Roles for Lambda function

Deployment

Make sure AWS environment has been set up, including access key, user, group, policies …

Pack and deploy Lambda example into AWS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sls deploy -r ap-southeast-2 -s dev
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (583 B)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..................
Serverless: Stack update finished...
Service Informations
service: serverless-example
stage: dev
region: ap-southeast-2
api keys:
None
endpoints:
None
functions:
serverless-example-dev-hello
  • Lambda “hello” function

A “hello” Lambda function has been created in Lambda after it’s deployed into AWS by “serverless”.

Lambda "hello" function

  • Events generated during Lambda function deployment

Deployment events generated during Lambda “hello” function deployed into AWS.

Events generated during Lambda function deployment

  • Add Lambda Trigger on AWS API Gateway

Manually create a Lambda Trigger. This time we use AWS API Gateway to trigger / invoke Lambda “hello” function.

Lambda Trigger created on AWS API Gateway

  • Exposed Lambda API Gateway

After Lambda Trigger created, an exposed RESTful interface for Lambda “hello” function.

Lambda API Gateway

Say “hello”

Set up AWS API Gateway trigger for Lambda “hello” function. Go to url, e.g.:

Function “hello” log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{
"message": "Go Serverless v1.0! Your function executed successfully!",
"input": {
"resource": "/serverless-example-dev-hello",
"path": "/serverless-example-dev-hello",
"httpMethod": "GET",
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch, br",
"Accept-Language": "en-AU,en-GB;q=0.8,en-US;q=0.6,en;q=0.4",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "AU",
"Host": "b5dyhej16l.execute-api.ap-southeast-2.amazonaws.com",
"Referer": "https://ap-southeast-2.console.aws.amazon.com/lambda/home?region=ap-southeast-2",
"upgrade-insecure-requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36",
"Via": "2.0 6884828476070d32978b45d03c1cc437.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "mvToMffe1AsUJNcMJKUh-Rx26oBJsRBe2n9I1df3xqIAIENPR_ku3A==",
"X-Amzn-Trace-Id": "Root=1-58aae2ff-0b0c5e4059cc97576211ba4a",
"X-Forwarded-For": "101.181.175.227, 54.239.202.65",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"queryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
"accountId": "624388274630",
"resourceId": "5jbqsp",
"stage": "prod",
"requestId": "51ba2876-f769-11e6-b507-4b10c8a6886a",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": null,
"cognitoIdentityId": null,
"caller": null,
"apiKey": null,
"sourceIp": "101.181.175.227",
"accessKey": null,
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": null,
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36",
"user": null
},
"resourcePath": "/serverless-example-dev-hello",
"httpMethod": "GET",
"apiId": "b5dyhej16l"
},
"body": null,
"isBase64Encoded": false
}
}

References

Factorial function implementation in Java 8

Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.paradise.function;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
* Created by terrence on 12/12/2016.
*/
public final class FactorialFunction {

public static final Map<Integer, Long> FACTORIAL_MAP = new HashMap<>();

public static final Function<Integer, Long> FACTORIAL = (x) ->
FACTORIAL_MAP.computeIfAbsent(x,
n -> n * FactorialFunction.FACTORIAL.apply(n - 1));

static {
FACTORIAL_MAP.put(1, 1L); // FACTORIAL(1)
}

private FactorialFunction() {

}

}

Unit test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.paradise.function;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* Created by terrence on 12/12/2016.
*/
public class FactorialFunctionTest {

@Test
public void testFactorialFunction() throws Exception {

assertEquals("Incorrect result", Long.valueOf(1), FactorialFunction.FACTORIAL.apply(1));
assertEquals("Incorrect result", Long.valueOf(2), FactorialFunction.FACTORIAL.apply(2));

assertEquals("Incorrect result", Long.valueOf(3628800), FactorialFunction.FACTORIAL.apply(10));
}

}

Fibonacci function implementation in Java 8

Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.paradise.function;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
* Created by terrence on 12/12/2016.
*/
public final class FibonacciFunction {

public static final Map<Integer, Long> FIBONACCI_MAP = new HashMap<>();

public static final Function<Integer, Long> FIBONACCI = (x) ->
FIBONACCI_MAP.computeIfAbsent(x,
n -> FibonacciFunction.FIBONACCI.apply(n - 2) + FibonacciFunction.FIBONACCI.apply(n - 1));

static {
FIBONACCI_MAP.put(0, 0L); // FIBONACCI(0)
FIBONACCI_MAP.put(1, 1L); // FIBONACCI(1)
}

private FibonacciFunction() {

}

}

Unit test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.paradise.function;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* Created by terrence on 12/12/2016.
*/
public class FibonacciFunctionTest {

@Test
public void testFibonacciFunction() throws Exception {

assertEquals("Incorrect result", Long.valueOf(0), FibonacciFunction.FIBONACCI.apply(0));
assertEquals("Incorrect result", Long.valueOf(1), FibonacciFunction.FIBONACCI.apply(1));
assertEquals("Incorrect result", Long.valueOf(1), FibonacciFunction.FIBONACCI.apply(2));
assertEquals("Incorrect result", Long.valueOf(2), FibonacciFunction.FIBONACCI.apply(3));
assertEquals("Incorrect result", Long.valueOf(3), FibonacciFunction.FIBONACCI.apply(4));
assertEquals("Incorrect result", Long.valueOf(5), FibonacciFunction.FIBONACCI.apply(5));
assertEquals("Incorrect result", Long.valueOf(8), FibonacciFunction.FIBONACCI.apply(6));

assertEquals("Incorrect result", Long.valueOf(13), FibonacciFunction.FIBONACCI.apply(7));
assertEquals("Incorrect result", Long.valueOf(21), FibonacciFunction.FIBONACCI.apply(8));
assertEquals("Incorrect result", Long.valueOf(34), FibonacciFunction.FIBONACCI.apply(9));
assertEquals("Incorrect result", Long.valueOf(55), FibonacciFunction.FIBONACCI.apply(10));

assertEquals("Incorrect result", Long.valueOf(12586269025L), FibonacciFunction.FIBONACCI.apply(50));
}

}

Remote debugging Java applications run on Tomcat

Enable JVM option to attach a remote debugger:

1
$ export JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005

“suspend” set to “y” is to let remote debugger start loading the application.

Now, start running Tomcat. JVM debugging port is bound on port 5005i on the machine runs on Tomcat.

Next, set up SSH tunnel mirror remotei host (ip-10-213-79-77.ap-southeast-2.compute.internal) 5005 port to localhost on port 5005. For example:

1
$ ssh -L 5005:ip-10-213-79-77.ap-southeast-2.compute.internal:5005 -l ec2-user ip-10-213-79-77.ap-southeast-2.compute.internal

You can start remote debugging in IDE like IntelliJ and debug the code since.

Web Components are coming

AngularJS is going to continue to succeed for some time. But change is inevitable.

Web Components are coming.

WebComponents create the ability to do all the sorts of markup-driven programming like AngularJS, ReactJS, but less ecosystem dependent. Because DOM is integration point for all the kinds of JavaScript frameworks. Web Components make it MUCH easier to interoperate between components.

The future isn’t here yet, but it will change fundamental assumptions about how a JavaScript framework should act and what it should be responsible for. Those shifts in assumptions frequently cause frameworks will drop out of the ecosystem quickly than expected.

Web Components

URL: https://www.webcomponents.org/

Perfection

Il semble que la perfection soit atteinte non quand il n’y a plus rien à ajouter, mais quand il n’y a plus rien à retrancher.

1
- Antoine de saint Exupery

It seems that perfection is attained not when there is nothing more to add, but when there is nothing more to remove.