Ever in need of a new project or excuse to learn something new, I recently decided to make use of the data that the various door sensors in my house capture.
As I mentioned in an earlier post, I use a service called PushingBox to in turn invoke an iPhone alert app called Prowl to tell me when the doorbell rings. I also use those services to tell me when the garage doors open, I've found it's kind of handy to know. However, for other doors like the front door or the laundry -> garage door, getting alerts for those are kind of spammy - and my wife would argue Big-Brothery.
But what if I could just capture the events and query for them later if the need arose?
To the RESTcue!
Seemed like a job for a RESTful API to me. I've played a little bit with writing such things using Spring Boot as I eluded to in the doorbell notification post, but it was pretty weak as an API. This time, though, I could employ a true RESTful API to capture and retrieve these events.
First, I had to determine what language/platform to use. I considered doing Clojure as I had started a pretty good tutorial online recently, or Kotlin, which I had been meaning to take up, but in the end, I knew that good old Spring Boot would make my life easy and I'd find new things to learn along the way.
Then I had to determine a data storage mechanism. I wanted to play with JPA but this seemed so simple that even basic object-relational mapping like Hibernate or MyBatis would be overkill for such a simple data structure.
Actually before I go any further, here's the data structure I came up with:
public class DoorEvent {
private Long id;
private String doorName;
private Date eventDate;
private Type type;
private String homeName;
}
public enum Type {
opened, closed
}
One could argue about the need for an enum
for the type of event, or the need to call it "type" in the first place (I generally feel that having a "type" variable in a class is a code smell) but I felt like it was an okay start.
Back to persistence. I felt like using a relational database was just too much hassle here, even if I embedded it. So I googled a bit and found ObjectDB which is an easily-embeddable Java-based object database, which also works well with JPA. It simply saves its data in a file on the filesystem. Perfect! No need for messy ORM.
I will note that one hiccup I found in getting Spring JPA to place nice with ObjectDB was that you also had to include the H2 database as a dependency for some screwy reason, even though it wasn't being used. Snippet from my pom.xml
file:
<dependency>
<groupId>com.objectdb</groupId>
<artifactId>objectdb</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
API Design Shenanigans
The next thing I needed to learn was how to properly design the API. I had a basic, albeit shaky, view on the philosophy of RESTful API design. Googling around showed me several good discussions, like this one where it was explained that your URLs are essentially locations to describe where a resource is. In the case of my API, the resource would typically describe one or more door open-or-close events. Also, resources are typically plural. So basically, the URLs are nouns.
Initially, I went pretty far down a path of using a URI scheme like
http://api.mysite.com/homes/[homeName]/doors/[doorName]
I wanted (and still want) the API to be flexible enough to work on multiple homes or properties, so homeName could be a variable.
I actually wrote a functioning version of the application that worked this way, but I found it ran afoul of RESTful guidelines. For instance, when getting events for the front door of my home ("Kerriell"), instead of:
/doors?homeName=kerriell&doorName=front
or
/homes/1/doors?doorName=front
or
/homes/1/doors/1
My API looked like /homes/kerriell/doors/front
.
I liked how this one URL looked, but this design aesthetic turned out to be problematic. For instance, the reference to one door event looked like /homes/kerriell/doors/front/123
, which is potentially confusing because it could imply that /homes/kerriell/doors/laundryRoom/123
is also a potentially valid yet distinct resource, when in fact the doorEvent ID is actually unique and as the code was written, both calls would result in the same response, looking up the doorEvent by ID only, ignoring which house and door name it belonged to.
It also required the caller to know the name of the home (and not a numeric identifier, another sort of no-no) instead of a cleaner version like /doorEvents/123
.
Hmm....
At this point, having a functional application, I decided to start this blog entry, and as I typed up my explanation for this screwy API... I realized it was all wrong.
Redesign
So I shelved the blog and rewrote the code to reflect a much saner RESTful interface:
Method | URI | Parameters | Description |
---|---|---|---|
GET | /doorEvents |
doorName (optional) type [ opened | closed ] (optional) homeName (optional) date (optional, of format MM-dd-YYYY or 'today') |
Gets door events, optionally for one specific date if date parameter is specified |
GET | /doorEvents/{id} |
None | Gets a single door event of given ID |
PUT | /doorEvents |
doorName (required) type [ opened | closed ] (optional, defaults to 'opened') homeName (optional, defaults to 'kerriell') |
Adds a door event |
With this version, the base URI is very simple: /doorEvents. Other information moves from being path variables to request parameters. I think what bothered me initially was that I felt the doorName should be on the path, but of course it can make sense to refer to the API without referring to a specific doorName, such as when getting all /doorEvents.
With request parameters doing all the differentiation, it still seems pretty clean. For example, all times the front door closed today:
GET
/doorEvents?doorName=front&type=closed&date=today
Spring Fun
All that URI design was actually the hard part. Using Spring Boot, Spring Data JPA, and Object DB, very little code needed to be written to capture and retrieve my door open/close events.
For instance, here's the code to save an event:
@RequestMapping(method = RequestMethod.POST)
ResponseEntity addDoorEvent(@RequestParam(value = "type",
required = false, defaultValue = "opened") String type,
@RequestParam(value = "homeName",
required = false, defaultValue = "kerriell") String homeName,
@RequestParam String doorName) {
DoorEvent event = new DoorEvent(homeName, doorName,
DoorEvent.Type.valueOf(type.toLowerCase()), new Date());
repository.save(event);
return ResponseEntity.created(new URI("/doorEvents/" +
event.getId())).body(event);
}
As you can see, most of that code is just method signature.
You might be thinking "Aha! But what about that repository.save(event)
call?" Well, I literally can't show you the code for that because I didn't have to write any. I did define an interface called DoorEventRepository
, but because of the magic of the JpaRepository, you get a dynamically-generated implementation with the basic CRUD methods for free.
I did add a bunch of interface signatures to my repository to allow various different query types, but again, only signatures, no actual implementation code had to be written by me.
List<DoorEvent> findByDoorNameAndTypeAndEventDateBetween(String doorName, DoorEvent.Type type, Date startDate, Date endDate);
List<DoorEvent> findByDoorNameAndEventDateBetween(String doorName, Date startDate, Date endDate);
List<DoorEvent> findByTypeAndEventDateBetween(DoorEvent.Type type, Date startDate, Date endDate);
List<DoorEvent> findByEventDateBetween(Date startDate, Date endDate);
You can find all of the code here on GitHub.
Putting It All Together
This is similar to my other home automation posts, so I won't belabor it: using my Vera and PushingBox, I created scenes that would call the API for each open/close event.
For example, my "Front Door Opened" scene would do a POST
to /doorEvents?type=opened&doorName=front
Querying for events becomes very simple. Looking at /doorEvents?date=today
, I get:
[
{
"id": 1916,
"doorName": "laundryRoom",
"eventDate": "2017-07-22 11:43:46 AM GMT",
"type": "opened",
"homeName": "kerriell"
},
{
"id": 1917,
"doorName": "laundryRoom",
"eventDate": "2017-07-22 11:43:55 AM GMT",
"type": "closed",
"homeName": "kerriell"
},
{
"id": 1918,
"doorName": "front",
"eventDate": "2017-07-22 12:18:35 PM GMT",
"type": "opened",
"homeName": "kerriell"
},
{
"id": 1919,
"doorName": "front",
"eventDate": "2017-07-22 12:18:38 PM GMT",
"type": "closed",
"homeName": "kerriell"
}
]
Of course, raw JSON isn't a great way to view events, so my next step is to write a front-end. I'm thinking Angular. That'll be Part 2.