Ruby API - Geolocation with external integration
1. build image
docker-compose build
2. create, migrate and seed DB
docker-compose exec api bin/rails db:rebuild
3. run image
docker-compose up
4. run tests (optional)
docker-compose exec api rspec spec
- first of all, need to export included in the repo Postman collection postman_collection.json into your Postman application
- next you have to make a simple authentication, setting any persisted user's credentials via
POST /login
request with next JSON body schema{"email": "<email>", "password": "<password>"}
.- it allows to login, generate and received JWT token, use it automatically on other requests via internal Postman collection variable;
- it is possible to create new user in Rails console:
docker-compose exec api bin/rails c
FactoryBot.create(:user, email: '<user@mail.com>', password: '<password>')
- or use default user, already existing in DB after seeding:
- JSON body
{"email": "user@mail.com", "password": "password"}
- in case automated setting of JWT doesn't work, you should set it manually into each request in Postman:
- copy JWT token value received in response body from
GET /login
endpoint onauth_token
key;- paste it as new Header into every single request using next template
Authorization: 'Bearer <JWT_token>'
.
- now, feel free to use any of four requests to manage geolocation records using next RESTfull endpoints using stored inside JWT token from previous step:
- create geolocation
POST /api/v1/geolocations
a request to store new location data received from IPstack external service by provided from current user IP address or URL in JSON payload with next schema{"target": "<ip_or_url>"}
;- show geolocation
GET /api/v1/geolocations/:target
a request to fetch data about geolocation by given IP or URL set in path string, implementation provides ability to work not only with IP and domain format, but even to set the whole URL with prefixes without supporting this on IPstack integration;- delete geolocation
DELETE /api/v1/geolocations/:target
a request to remove geolocation from DB using the same payload schema as in previous one;- list geolocations
GET /api/v1/geolocations
the last extra request to have ability showing reduced data about all records via simple index endpoint.
1. Understand the Requirements:
- carefully read and understood the problem definition.
- identified the core functionalities the API should provide: managing doctor availabilities, patient booking/editing appointments, and viewing availability.
2. Select the Technology Stack:
- chose the programming language and framework.
- RoR, PostgreSQL, Docker-Compose, JWT, Pundit, DRY-validation, Alba, Faraday, Swagger, RSpec, FactoryBot
3. Design DB & implement Models:
- determined the data models required for this system.
- created
Geolocation
model with:
- required
ip
string attribute;- optional
url
string attribute;- string
type
attribute;- float
latitude
andlongitude
attributes;- jsonb
location
attribute with all additional data received from 3rd party service.url
attribute will have a role like a caching layer to avoid redundant heavy call to external service in case if we already know which IP address is related.- perhaps, create separate related table to store
location
JSON data there, TBD...- created two indexes for
ip
andurl
(for second one only for cases when value is present) columns for faster searching.- created uniqueness index for
ip
column to avoid duplicated records in DB.
- stored only uniq records by IP or URL value.
- managed cases when there is already record with given IP but with empty URL value.
- added default Rails validation on model layer via creating own validators.
- implemented
find_by_ip_or_url
scope to search by target value in both attributes via one query.- created simple
User
model to be able use and showPundit
authorization.implement main model for storing geospatial data usingpostgis
gem.- designed and use
Service Object
to encapsulate and manage business logic in separate abstraction.
- service objects represent a single system action such as adding a record to the database or sending an email.
- service objects should contain no reference to anything related to HTTP, such as requests or parameters.
- seeded DB with default data.
- created complex
db:rebuild
rake task to run all DB related commands per one time.- final DB schema is next (see screenshot below):
4. API Endpoints:
- defined the API endpoints based on the requirements.
- ensured that the API follows RESTful principles (HTTP methods like GET, POST, PUT, DELETE, status codes, etc.).
- created couple of CRUD endpoints for ability to manage geolocations records.
- it's first version
V1
of API, so we added/v1
into path and moved controllers intoV1
module according to the best practices of API design and implementation.- used
:target
params as default instead:id
on show and destroy endpoints.
- moreover, allowed
target
value to contain IP addresses, domains and even URLs with prefixes via set up the constraints,- note that integrated
IPstack
service works only with domain addresses in path, so ability to receive, validate and store whole URL addresses will be useful in the future,
- use
%2F
combination instead single/
in case when need to pass URL prefix, for instancehttps://<domain>
.- need to dive deeper into ability to receive, validate and handle two separate properties in request payloads for
IP
andURL
values instead one commontarget
as is implemented now, TBD...- used
Alba
gem for serialization.
- potentially we can convert all keys to
lowerCamelCase
adding one command in base serializer.- used
JSON API
standard for request payloads and response bodies.- used
Swagger
framework to test and create automatic-generated documentation.
5. Integration with IPstack 3rd party service:
- created simple class to call
IPstack
3rd party service usingFaraday
gem.- created separate class to handle errors and transform response.
- implemented these classes as agnostic abstraction and used inside of only one service object for better potential replacing with another integration.
6. Authentication and Authorization:
- implemented user authentication to ensure only authorized users can interact with Geospatial data.
- used
Pundit
gem to authorise users permissions.- used
JWT
gem to authentication users.- created simple
login
endpoint to authenticate current user by JWT.
- in
Postman
collection implemented smart request based via JS pre-request script to login in API from outside, receive JWT token, store in PM variable and automatically use it in all other endpoints for providing ability to make all endpoints secure, not available to public.
7. Validation and Error Handling:
- added default Rails validation on model layer via creating own validators.
- implemented robust error handling to provide meaningful error messages through whole API.
- handled multi error cases according to JSON API specification, especially for contract and model validations when there might be several errors.
- it is possible not pass error message to response body if we don't want to show any exactly internal errors in client side.
- added validation for incoming data to prevent processing requests with invalid payloads.
- used
Dry-validation
gem and created a short contract.- added addition DRY rule to verify if one property contains any of both value formats.
8. Documentation:
- created this README.md file that explains how to run and use the service.
- added developer notes that were written during implementation.
- included Postman collection postman_collection.json into project for sharing with other team members.
- see more about all API endpoints described in section How to interact with API via Postman above.
9. Testing:
- used
FactoryBot
gem and created factories for each model- wrote couple unit tests to ensure the reliability of the code.
- created models specs.
- added validators own specs.
- created error handler and error objects unit tests.
- added contract own specs.
- created specs for
IPstack
lib classes.- added service object specs.
- cover all happy and unhappy cases on all endpoints with own integration test using Swagger framework and generate very useful and helpful documentation.
- visit
<server>/api-docs
you can see automatically generated API documentation like on screenshot above.- used
simplecov
gem to check amount of covered code with tests.
10. Deployment:
- built API application inside of docker container.
- used
Docker Compose
tool to manage API container with DB in separate one.- created one common
db:rebuild
rake task for ability to create and migrate DB on the begging of set upping API on current environment or reseed DB any time in easy way.
11. Future Improvements:
- Logging: Implement logging to track API requests and responses for debugging purposes.
- use common Rails approach or integrate any 3rd party service, aka
Rollbar
orCloudWatch
.- Rate Limiting: Consider implementing rate limiting to prevent abuse.
- Caching: Implement caching for repeated requests to improve performance.
- Notifying: Consider integrate emailing outside app to better notification of different events.
- use secrets to manage important environment variables.
- perhaps, create separate table to store
location
JSON data there and add one-to-one relation togeolocation
table...- instead using self-written regexp for URLs, discover any solution out of the box for better contract and model validations.
- consider ability to return list of records on searching by URL due to potentially possibility to have several records in DB with different IPs but related to the same domain.
- add background job to fetch and store data about IP or URL not existing in DB in new
geolocation
record.- hide internal 500th errors and not send them in response body.
- refactor base error class to work with the errors array more natively.
- implement different user policy scopes according to new roles data modeling, for instance.
- potentially implement ability to use other 3rd party service for receiving location data with managing whole URL addresses instead just domain part now.
- maybe at least in
V2
version of API it would be better to manage and process different properties in request payload for IP and URL values instead one common.- extent Swagger documentation with examples of request payloads and request bodies schemas foe every single endpoint.
- add on
index
page at least pagination to avoid too long responses if there are a lot of records in DB.- consider
postgis
library to improve managing of geospatial data.