This project is (supposed to be) a microservice in a home automation system that manages user/device relationship.
To show how I write basic REST APIs and try out some libraries.
- Spring Boot: web framework
- Hibernate: for ORM and validation
- MySQL: as the primary database
- Memcached for caching and Nginx as reverse proxy
- Swagger/OpenAPI: for API documentation
- SLF4J: logging
- Docker Compose and Kubernetes: for deployment
There are four entities managed by this service, User
, Device
, DeviceType
and Binding
. User
and Device
represents people and their appliances in the system. DeviceType
is a class or category of devices, e.g., Hue Light
or thermometer
, that take the same commands or report status in the same format. Binding
represents ownership of devices, a user can only control or read status of a device if there is a Binding
record between them.
The schema is in /db/schema.sql
file. Some brief notes is listed below.
name
column inUser
should be namedusername
and is unique in the table.password
field should already be hashed before reaching this service (perhaps in API gateway or some previous layer). Therefore, no hashing/salt being performed here (security is delegated to the previous layer).- For the same reason,
Binding
creation does not check ifpinCode
matches the device's code. - Probably could have add a unique constraint on (manufacturer, modelname) pair in
DeviceType
.
The project is organized in features, e.g., User
, Device
, etc. In each feature area, there are three simple layer. controller
as the API's entry point, repository
for abstracting access to databases and service
for encapsulating external services or aggregating calls to repository
.
Basic CRUD API sets for each entity except for Binding
. Due to the limited fields in Binding
, there is no point to have update API.
PUT
are used for full update (replace the entire state/object), which means that the request body must contain the entire object. PATCH
is used for partial update but the request body must contain at least one updatable field. Plus, if any updatable field is present, it must be valid.
Binding
's query API support embedded entity, setting query parameter entities
to device
will query Binding
with their associated Device
.
Swagger source is in /doc/api.yaml
and the API document can be found here.
Place a property file or yaml file in the locations Spring
check. Some important items are listed below. An example file can be found in /config/application.properties
.
spring.datasource.url
: MySQL urlspring.datasource.username
andspring.datasource.password
: username and password of MySQLspring.datasource.driver-class-name
: MySQL drivermemcached.cache.servers
: Memcached url
./gradlew build
: building a fat jar
./gradlew bootRun
: for running it locally
./gradlew build docker
: for building a fat jar and creating a docker image
The server expose port 8080
1. Docker Compose
Run docker-compose up -d
to spin up nginx, memcached, api server and MySQL.
Nginx config file is in /config/nginx.conf
. MySQL schema (/db/schema.sql
) will be imported with --init-file
command. MySQL password will be set to environment variable MYSQL_PASSWORD
. /scripts/cleanup.sh
can be used to cleanup dangling volumn and such.
2. Kubernetes
Deployed using minikube.
All yaml files are stored in k8s/
directory:
pv001.yaml
: Persistent volume withhostpath
for MySQL.mysql-pw.yaml
,mysql-schema.yaml
:Secret
for MySQL password andConfigMap
for database schema.mysql.yaml
: Persistent volume claim for storage, deployment for a single MySQL instance and headless service for discovery. Schema will be mounted at path/docker-entrypoint-initdb.d
and tables will be created on startup.memcached.yaml
: A headless service and a single instance deployment for memcached.api-config.yaml
:ConfigMap
s containing nginx and api server's config file.api.yaml
: A deployment that manages pods containing one nginx server as reverse proxy and one api server. A service for exposing the api server usingNodePort
.
Readiness and liviness probe with path /ready
and /healthy
is configured for the api server container.
Build
Link minikube with the local docker daemon
eval $(minikube docker-env)
Build
./gradlew build docker
Deploy
Deploy the objects in the following order.
pv001.yaml
mysql-pw.yaml
,mysql-schema.yaml
mysql.yaml
memcached.yaml
api-config.yaml
api.yaml
Retrieve the service url with minikube service user-device --url
Scripts in directory examples/
show basic API calls of UserDeviceService
.
These scripts assume the server listening on localhost:8080
. If not, modify the HOST
& PORT
variables.
If the service is deployed using kubernetes
, use kubectl get pods
to get one of the pod
ID of the API server, e.g., user-device-api-7c4456f6b5-57qmf
. After that, use kubectl port-forward user-device-api-7c4456f6b5-57qmf 8080:8080
to consume the service using localhost:8080
.
The description of each script is shown below:
01_device_type_basic.sh
,02_device_basic.sh
,03_user_basic.sh
: CRUD examples forDeviceType
,Device
andUser
.04_binding_basic.sh
: CreateBinding
between aUser
&Device
.05_binding_query.sh
: Different ways to queryBinding
.