Permalink
Browse files

Update readme: add Features and Design Decisions

  • Loading branch information...
MitchellMcKenna committed Sep 3, 2017
1 parent 97d3be3 commit f7a7e345965f34781b9ad3ffa01cf3acf3b22868
Showing with 36 additions and 4 deletions.
  1. +36 −4 readme.md
View
@@ -1,12 +1,13 @@
# Product Sales API
# Laravel JSON API Example
Example of a simple product sales API.
Example of a simple API, built using [Laravel](https://laravel.com) and implementing the [JSON API](http://jsonapi.org) spec.
* Built using [Laravel](https://laravel.com).
* Follows [JSON API](http://jsonapi.org) spec.
In this example API, there is products and sales orders of those products.
## API Endpoints
There are CRUD endpoints for both Product and Order resources. The is also an example of how to implement a more complex endpoint which calculates the top sellers over a given time period.
```
GET|POST /products
GET /products/top-sellers
@@ -20,6 +21,19 @@ API docs: [http://docs.productsalesapi.apiary.io](http://docs.productsalesapi.ap
The API docs include more detailed examples of the endpoints, query params, input and and response payloads. Written using [Blueprint](https://apiblueprint.org), they can be updated by editing /apiary.apib.
## Features
* Validation - utilizes laravel form request validation to keep lean controllers.
* API Documentation - via Blueprint and Apiary.
* JSON API compliant responses - with pagination via Fractal.
* JSON API compliant errors - validation errors and other error responses.
* Resource Controllers - for clean separation of responsibility.
* Complex Raw Query Example with Pagination - Top Sellers uses a Query Object and manual LengthAwarePaginator.
* Caching - ResponseCache middleware to cache all successful GET requests. Cache is busted via the Observers on Create/Update of Orders/Products.
* Profiling - performance via Clockwork.
* Postman collection - with example API calls.
* Unit Tests - utilizes an in-memory database and db factories to simulate real calls.
## Requirements
* [Composer](https://getcomposer.org/doc/00-intro.md#globally)
@@ -59,3 +73,21 @@ You can make API calls using [Postman](https://www.getpostman.com/). In Postman:
1. Import the collection from `/postman/Prouduct Sales API.json.postman_collection`
2. Manage Environments > Import from `/postman/Product Sales API (Vagrant).postman_environment`.
## Design Decisions
* JSON API spec was chosen to provide a consistent request, response and error structure which promotes RESTful design. The flat document structure and relationships eliminate duplicate data to minimize response sizes. If you prefer the simplicity of embedding related resources in the same resource simply change the [Serializer](https://fractal.thephpleague.com/serializers/) in Fractal calls.
* MySQL was used as the database technology as in this app's instance, the data required is structured and relational data. Alternatively any RDBMS would allows for maintaining the relationships and normalized data such as PosGreSQL.
* Eloquent was used as the database layer as the ORM provides an extremely simple interface for most database calls. Alternatively Doctrine ORM could be used if DataMapper pattern is preferred.
* One perceivable downside to using Eloquent is that related objects are retrieved using a separate db call instead of a join. A previously highly debated topic in the Laravel community. This is because the related table, and also for example one to many relationship calls, could result in many overlapping column names so a separate calls keeps this simple. Eager loading avoids N+1 queries but 1 additional db call is done. Alternatively if this is an issue you can do this using a join manually using eloquent query builder.
* A Query Object was used for the aggregate raw query for Top Sellers as such is not possible using eloquent query builder or a local scope. Alternatively since the query isn't being used anywhere else a private functions directly in the controller could have been used. Another well accepted approached would be to use Repositories or a Service instead.
* Models were injected via dependency injection rather than using static calls to their facades to simplify unit testing.
* Fractal was chosen for simple response creation in JSON API format. Alternatively Laravel 5.5's [API Resources](https://laravel.com/docs/5.5/eloquent-resources) could be used to compile the responses in JSON API format manually.
* Fractal transformers are used to convert objects to their response formats instead of modifying the serialization toArray() on the model, which allows for multiple forms of serialization to be supported if need be down the road.
* Top Sellers' `quantity` was included in the `meta` offset for objects instead of part of the object payload to clearly convey this is not data which can be PATCH'ed by the client.
* ResponseCache coupled with Observers is used to provide a simple caching layer without cluttering controllers with caching logic. Another acceptable approach would be to use Repositories with a Cache Decorator.
* Most GET requests (which have a cache-miss) issue 2 database calls; 1 to get the object(s) requested and one for count of total objects. The 2nd db call is for pagination using LengthAwarePaginator, it can be avoided by using a generic Paginator instead and the client can assume they are at the last page once they get to an empty page of results. However I think this extra db call is worth it so the client knows how many pages of results there is up front, especially necessary if they need to show total number of pages on their end, but also so they can avoid the extra API call of empty results at the end.
* The Order model is setup to eager load it's related Product automatically, this is to avoid N+1 queries when retrieving orders because there is currently no use-cases where orders are not needed without knowing it's related Product.
* Blueprint was used for API documentation as Blueprint is pretty much simple markdown syntax. This was chosen over say Swagger/OpenAPI annotations in phpDoc blocks to keep controllers clean. Alternatively OpenAPI could be stored in a yaml file.
* Clockwork was used for local performance profiling as Laravel-Debugbar does not work natively with API calls.
* Unit tests utilize an in-memory database, db factories, and the RefreshDatabase trait to simulate real db calls. Alternatively if the unit testing suite becomes larger and begins to take too long to run we can switch to classic PHPUnit TestCase and mocking all dependencies.

0 comments on commit f7a7e34

Please sign in to comment.