fugu13 / challenge

A code challenge

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

A Coding Challenge

A job I'm talking to asked me to do a Java programming challenge, and while I've written a lot of Java/JVM stuff, it had been a while since I'd done several things involved in the requested project, notably: start a webapp from scratch, use Maven to manage a project (really, I've been using Gradle since, well, about Maven 2), and work on a typical CRUD-like API project. Also, I'd never really used MyBatis (I think I briefly touched it back when it was iBatis?), the requested database technology.

I knew I wanted to use some technologies I have been using and liking lately, mostly Flyway for database migrations and Redux-Observable (with ES 6) for the web client. I also wanted to use some technologies I've been looking at but haven't really had the chance to use, in particular Spring Boot. I experimented with using Jersey, another technology I like a lot, but decided that Spring MVC played better with Spring Boot and my needs.

So anyways, here's the project. It's an API for managing simple Supplier objects (think name, address, contact info) and creating and flexibly querying simple timestamped Transaction with payload objects related to them. Along with the API there's a basic SPA interface.

Some things I like

Every individual piece of code is simple and understandable. Classes and methods are all short. Each tier has a pretty clean separation from the others, helping out with this. For example, I initially went without DAO tier... but then saw the web tier needed to handle database exceptions that required close knowledge of how the database did stuff, leading me to add the DAO tier despite this being a pretty simple app.

The relationships between pieces of code are also very simple to understand, largely due to Spring's autowiring. The web controllers (which use validators) are wired to the data access objects are wired to the database mappers, and domain objects appear throughout. Web controller exceptions are handled by exception advice.

Spring Boot takes autowiring to the next level. A lot of configuration happens effortlessly just by including modules in the build. For example, H2 is included (and no other database config specified), so Spring Boot happily creates an in memory database and configures it as a DataSource. Flyway is included, so Spring Boot automatically looks for migrations in certain spots (though I had an issue getting test migrations autodetected) and applies them as needed, without any code or commands at all (though commands are also automatically available if desired). I was initially a little worried about this level of magic, but so far the benefits have been very good, and pretty much any of the magic can be overridden.

Some Choices Related to the Challenge

The idea is that millions of transactions a day might be created (in a real version of this API), so several of the choices I've made relate to that. For example, IDs are generated in the web application tier (currently UUIDs, though I'd probably change to something like Snowflake IDs in a real app), which prevents contention of database generated IDs. Timestamps are still left to the database though, as those can be generated contention-free. Next, the API only supports paginated transaction retrieval, since theoretically there could be many millions. Also, as transactions are constantly being added, using start/offset based pagination would result in weird artifacts (in the "real world", so all pagination is by creation time (plus the unique id, to make start point unambiguous).

Since the only database is a testing one, I didn't create many indexes to support the complex filtering in the API, though I did create one on transactions for supplier + creation time, since presumably each supplier will have many many transactions. If used with PostgreSQL in production, something like a multicolumn GIN index may well be a good choice for slicing through columns where unique values only match a few transactions (created and data).

I looked into ways to manage Javascript build infrastructure with maven, but nothing really stood out to me, so I kept things simple by using static HTML and Javascript and CSS (all autodetected and made available by Spring Boot). I'd have liked to include Javascript tests, but that's pretty awkward without a build infrastructure, so they're left off. I'd have also liked to use React, but for simplicity my Redux render happens via Handlebars JS. That gets awkward around focus and forms, so replacing that would be one of the first changes I'd make. Luckily, even though I don't have JS transpiling, since this isn't a real web form I could already use ES 6 (WARNING: developed in Safari, should work but not tested in other modern browsers, definitely no IE less than recent Edge).

For testing, I'm really happy with Spring's Mock MVC testing. It tests the full integration stack except the very last bit, actual HTTP traffic. Everything else about the web request is there -- you talk to a path, you have headers and body, etc. Basically, it tests the actual behavior of everything you actually create for the webapp, at least one like this. But it isn't that last HTTP level, which provides one big advantage: the test environment has a lot more access to the internals of the app, since communication isn't actually happening over HTTP, and this makes it much easier to do a number of things, though the one that mattered most to me is better logging on failures.

I chose to write all integration tests. I knew real integration tests were essential, so I started there. When I looked at writing unit tests partway through, I quickly noticed it was hard to find ones to write that tested anything interesting and also weren't entirely subsumed by my integration tests.

A pattern you'll see in my integration tests is, I don't use database fixtures, so the first few steps of a test are almost always setting up a few entities, even when the test is about retrieval. This is deliberate. I have often seen database fixtures diverge from what they would be if created by the APIs, so where it isn't an undue burden, I like to test the entire data lifecycle.

One thing that I very much wanted to do with my tests, but did not get to, is property-based testing. The problem I ran into is, the excellent junit-quickcheck uses @RunWith, but so does SpringRunner, and JUnit only supports a single RunWith annotation (understandably). The normal advice is to manually do what one of the desired RunWith annotations does, but both of these are quite complex. Property-based testing would have made it much easier to test the full scope of the API. For example, instead of testing a particular set of Transactions with a particular set of queries for the right counts, in a property-based test a large number of random sets of Transactions would be generated, and queries dependent on them as well, and then the counts checked. And in addition to running a large number, the exact ones ran would change every time, exercising many nooks and crannies of the API in a way that isn't feasible with manually specified data.

Various Instructions

To compile

Inside the root directory, run mvn compile

Run from IntelliJ

To run the project inside IntelliJ, there's a Main class with a main method inside com.example.

Run from the command line

Inside the root directory, run mvn exec:java@main.

Build a WAR

Inside the root directory, run mvn package, the war will be in the target subdirectory.

Deploy the WAR

Copy the war into the webapps folder (perhaps changing the name, if desired). Go to the management interface and click start, then visit the app. Tested only in the most recent Tomcat.

Run tests in IntelliJ

They're in the com.example.integration package in the test tree.

Run tests from the command line

Inside the root directory, run mvn test.

Client

For everything related to the simple client that sends some random transactions, see the entirely separate project https://github.com/fugu13/challenge-client , which does exactly that and nothing more. Instructions for building and running the client are in that project's README.

About

A code challenge


Languages

Language:Java 72.5%Language:JavaScript 18.8%Language:HTML 8.7%