├── API_FIRST.MD └── README.md /API_FIRST.MD: -------------------------------------------------------------------------------- 1 | # API FIRST, MODELLING EXERCISE 2 | ## What is API FIRST 3 | 4 | Is a concept that treats your API's as a first class citizen. It is standing to have consistent and reusable API's written in an API description language to Establish the contract. 5 | 6 | There are some advantages about using this approach, but my favourite one is about reducing the feedback loop. Writing the API First, is useful to validate the design with any pair. Or even it is encouraging to you to make questions before jumping to the production codebase. 7 | Is always cheaper, if you have to do changes, if they are detected at early stages. 8 | 9 | ## Readings 10 | 11 | * [API First](https://swagger.io/resources/articles/adopting-an-api-first-approach/) 12 | * [API First How To with Swagger + SpringBoot](https://easyitblog.info/posts/api-first-approach-with-swagger/) 13 | 14 | ## The Exercise 15 | 16 | ### Our Meetup Tracker 17 | 18 | ![Meetups!](https://cdn.iconscout.com/icon/free/png-256/meetup-5-739520.png) 19 | 20 | Imagine you want to design an API in order to keep track of the events your community are organising since you're tired of paying to Google Meetup. 21 | 22 | ``` 23 | When an event is created, we have to define: 24 | 25 | * Date and time (in the future, otherwise we could not create the event) 26 | * Title and Description (only title is mandatory) 27 | * Location (Locations are already saved in the system, currently they are Main Office, Cool Cowork and Remote 28 | * Speaker 29 | * Facilitator (is optional) 30 | ``` 31 | 32 | For an already created event, people can join if they were already registered in our platform. Speakers and Facilitators must be registered as well 33 | 34 | ``` 35 | For registering a new person in our platform, the system needs the next info in advance: 36 | 37 | * Name 38 | * Last Name 39 | * Email (must be unique in the system and for sure a valid email) 40 | 41 | All fields above are mandatories. 42 | ``` 43 | 44 | Bear in mind, an event is the past is closed for updates, so nobody could join as attendee. 45 | 46 | Once the event is passed is important to know if a person definitively went to the meetup, to measure how much absent people we have in our community. 47 | 48 | After some days, people who really attended to the meetup, could set how they liked the event, adding a score from 0 (they didn't like) to 5 (Best event in the world!) To do that, they will receive a notification to their email. 49 | 50 | Bear in mind, the people registered are so responsible, so if they know in advance they could not be able to attend, they can remove itself from the event. Even they can be removed from the platform as well. 51 | 52 | Also is important to allow complex searching over the events. No matter if they passed or if they are set in the future, we'd like to be able to filter by: 53 | 54 | ``` 55 | * Date Range: From/To if To is empty then To=TODAY 56 | * Location: Main Office, Cool Cowork or Remote 57 | * Title: It must be a "contains" and no case sensitive 58 | ``` 59 | 60 | In addition, we'd like to receive from the search, some stats based on the people registered to the event: 61 | ``` 62 | * Title 63 | * Date 64 | * Location 65 | * Speaker 66 | * Facilitator 67 | * Number of Registered People 68 | * Number of Attendees 69 | * Number of Absents 70 | ``` 71 | 72 | Finally, we'd like to have an easy way to collect all the people who acted as speaker/facilitator, and how many times, the data should be as follows, no matter if it is a speaker/facilitator 73 | 74 | ``` 75 | * Name: Name + Lastname 76 | * Events: Counting how many times the person acted as speaker/facilitator 77 | ``` 78 | 79 | ### Design the API in advance 80 | 81 | Please design an API in advance (you can use for example the [swagger editor](https://editor.swagger.io/) which later has tools to export the api to php, java...) in order to model the endpoints needed to cover the exercise. 82 | 83 | * ℹ️ At this point, we only want to know: 84 | * How many endpoints do we need? How they are going to be named? are some of them idempotent, not? why? 85 | * What kind of HTTP Verbs we have to use 86 | * What are going to be the HTTP Status to represent, success or ko 87 | * Our Data Model to represent the resources 88 | 89 | As you see, API First is encouraging to you to make questions at design time! 90 | 91 | ### Go ahead for some Outside in TDD! 92 | 93 | Once you are happy with the API designed in the previous step, let's go a little bit further, and design the whole solution using Outside In TDD (From your API to the Datasource) 94 | 95 | Swagger Editor has a tool to export an API First YML file to code 96 | Also you can use a library like [RestAssured](https://rest-assured.io/) or [Cypress](https://circleci.com/blog/api-testing-with-cypress/) 97 | 98 | ℹ️ Just one last thing, the datasource is up to you, if you want to have a real database running at backend side is ok. If you want to have just an in-memory implementation, it is also fine 99 | 100 | :warning: Don't submit any PR to this repo! just fork it and do your implementation there ;) 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rest API. Best Practices to design at the 2nd Maturity Level 2 | 3 | ## Introduction 4 | 5 | ### REST 6 | 7 | - What is REST? 8 | 9 | REST is an acronym for Representational State Transfer and an architectural style for distributed hypermedia systems. 10 | 11 | - What is a REST API? 12 | 13 | A Web API (or Web Service) conforming to the REST architectural style is a REST API. 14 | 15 | - What is the architectural style then? 16 | 17 | It is based on six principles: 18 | 19 | #### Uniform Interface: 20 | 21 | Rest is resources oriented, so is important to identify a right level of abstraction of the resources involved and their interactions. 22 | 23 | #### Client Server: 24 | 25 | Clients and Servers can evolve isolated since the concerns are separated (but keeping in mind the interface must be respected) The main idea is to separate the user interface concerns (client) from the data storage concerns (server) 26 | 27 | #### Stateless: 28 | 29 | The server can't store any session at their side, so every request made to the server needs to contain all the information, to allow to the server to execute the requested action properly. 30 | 31 | So the session state needs to be keep at client side 32 | 33 | #### Cacheable: 34 | 35 | Retrieving resources should be cacheable. (This is why using a Body Payload for a GET call is considered a bad practice) 36 | 37 | #### Layered System: 38 | 39 | In Rest it is fine to hold the API in a server "A" and then save the resources in a server "B" (and even having the security in a server "C") 40 | 41 | All the above constraints help you build a truly RESTful API, and you should follow them. Still, at times, you may find yourself violating one or two constraints. Do not worry; you are still making a RESTful API – but not “truly RESTful.” 42 | 43 | ### Richardson Maturity Model 44 | 45 | ![Maturity Model Pyramid](https://martinfowler.com/articles/images/richardsonMaturityModel/overview.png) 46 | 47 | Is a way to measure how mature is your REST Api based on 4 models, from 0 to 4. I'd like to encourage this is not a standard. In fact, there is no standard defined to say if a REST Api is good enough or not. But the truth is, the Richardson Maturity Model was quickly adopted by the IT Industry, at least ot have "something to follow" to let you know you're doing well 48 | 49 | 50 | #### Level 0: 51 | 52 | ![It's free!!!](https://i.kym-cdn.com/entries/icons/facebook/000/005/169/Screenshot_67.jpg) 53 | 54 | If you have an API defined over the Https protocol to transfer data, then you are already in that level. It's free! 55 | 56 | ### Level 1: 57 | 58 | This level aims for having a good abstraction level based on resources. Following the plural over the singular, and not including verbosity at any endpoint 59 | 60 | Examples of good naming :white_check_mark: 61 | 62 | - /employees 63 | - /departments 64 | - /employees/9e0343b9-a873-4db7-813e-fdeb68305d3b/departments 65 | - /candidates/search?name=jhon&page=1&offset=20 66 | 67 | The first endpoint can be used to create/update a single employee, or even to retrieve all the collection 68 | 69 | The second endpoint is similar to the first one, but based on departments 70 | 71 | The third can be used to update the departments of a given employee. Also, to retrieve the departments where the employee is added (if the key is found) 72 | 73 | The last one is a search endpoint following the [Google Semantic](https://www.google.com/search?q=google), it is retrieving the first page with only 20 candidates who are named as John 74 | 75 | Examples of bad naming :x: 76 | 77 | - /employee (not plural) 78 | - /departments/create (verbosity is not allowed) 79 | - /getEmployeeById (Please don't) 80 | - /search?type=candidate&name=jhon&page=1&offset=20 (Avoid using flags, wrong abstraction level) 81 | 82 | ### Level 2: 83 | 84 | Now the right usage of Http Verbs and Http Status Codes are important. 85 | 86 | Since the endpoint does not allow to be verbose, we have to use the right Http Verb to specify the kind of action we want to execute. In addition, is a good practice to choose the right Http Status Code to return if the action requested were succeeded or not. 87 | 88 | - POST (Use POST only for creating new resources, please return 201) **This operation is not idempotent** 89 | - GET (Use GET to retrieve data without changing the status of the server, please return 200, remember then it can be cached) 90 | - PUT (Use PUT to update a whole existing resource in an idempotent way, please return 200) 91 | - PATCH (Use PATCH to update partially an existing resource OR changing the state in a non-idempotent way OR implement a command that is out from the REST convention OR many other reasons... I just listed the most typical) **This operation should not be idempotent** 92 | - DELETE (Use DELETE to remove a resource from the server, please return 200) **This operation is not idempotent** 93 | 94 | As you notice is important to keep in mind the idempotency principle. Some operations must be idempotent by default, not others. If you are not familiar with idempotency, this is **a mathematical principle that is saying you'll always have the same result, no matter how many times you execute it** 95 | 96 | ### Level 3: 97 | 98 | ![HATEOAS as XML](https://upload.wikimedia.org/wikipedia/commons/6/60/Rest-from-get-to-hateoas-25-728.jpg) 99 | 100 | Since this level adds complexity at client/server side. It is not recommended for small API's. Even some people consider it as an [overkill](https://www.google.com/search?q=hateoas+overkill&oq=hateoas+overkill), also it is better to keep the focus in the 2nd level for now 101 | 102 | ## Further readings: 103 | 104 | * About [REST](https://restfulapi.net/) 105 | * About [Maturity Models](https://martinfowler.com/articles/richardsonMaturityModel.html) 106 | 107 | ## 2nd Level as the Pragmatic Approach 108 | 109 | Level 2 is valid enough, you will find it in the 90% of the cases, so it is an acceptable approach, since it stands to encourage the good usage of namings (so then a right abstraction level) status codes a right verbs. 110 | 111 | Even we could say the level 2 is the pragmatic one, since it is pushing for the having some best practices but avoiding the complexity of the level 3. 112 | 113 | The next set of practices will help you to model a Rest Api focusing in the 2nd level and adding an extra layer of best practices to consider 114 | 115 | ### Your endpoints are resources, not a representation of your datasource 116 | 117 | It is important to identify the objects which they will be presented as resources in an agnostic manner. 118 | 119 | One of the most common pitfalls, is to design the endpoints keeping the datasource on mind. I mean, imagine you have a Relational Database, and it contains VEHICLE, CAR, BIKE, PIECE, VEHICLE_PIECE... as examples 120 | 121 | Then it is wrong to have dedicated endpoints for each table, specially if VEHICLE is so abstract, and it has no meaning by itself. So this is wrong: 122 | 123 | * /vehicles 124 | 125 | * /cars 126 | 127 | * /bikes 128 | 129 | * /pieces 130 | 131 | Probably just /cars and /bikes would be enough, and to retrieve pieces you could model in this way: 132 | 133 | * /cars/pieces: To deal with the full catalog of pieces for cars 134 | 135 | * /bikes/pieces: To deal with the full catalog of pieces for bikes 136 | 137 | * /cars/{id}/pieces: To deal with the pieces they were used to manufacture a specific model of car 138 | 139 | * /bikes/{id}/pieces: To deal with the pieces they were used to manufacture a specific model of bike 140 | 141 | 142 | In addition, you could have more than just a single data source, what if you have also to serve images that they are saved in a local volume named /vehicles/images ? Again don't map your API against your storage. Your interface must not be attached to those kinds of details, and it can be modelled in a different way. Examples: 143 | 144 | * /images: full catalog of images if needed returning just the links to those images 145 | * /cars/{id}/images: catalog of images for one car 146 | 147 | Finally, is fine to have different endpoints which are modelling the same data in different ways. The data could have multiple representations, not just a single one. Examples: 148 | 149 | * /cars/engines 150 | 151 | There is no "engines" table, in fact an engine is a "big piece" so probably it is stored at PIECE table. But we're representing the engine as part of our API as an important resource. 152 | 153 | 154 | ### Please use the right verbs 155 | 156 | Remember to don't make your API verbose. That means verbs are not allowed as part of the endpoint. Just use: 157 | 158 | * *POST:* To create a new resource. The information about the new resource to create, is inside the payload (body) of the request. *This operation is not idempotent* that means, if you retry the request, it will create the same resource again with a different id. Be carefully with that. 159 | 160 | ``` 161 | curl -d '{"newCar":{"id":"abc-def-ghi","brand":"jkl-mn-opq","model":"New Model to hit the market!"}}' -H "Content-Type: application/json" -X POST http://server:port/v1/cars 162 | ``` 163 | 164 | * *PUT:* To update a whole existing resource. The information about the updated resource is inside the payload (body) of the request. *This operation must be idempotent* No matter how many times the PUT is executed, the result must be the same 165 | 166 | ``` 167 | curl -d '{"updatedCar":{"brand":"rst-abc-xyz","model":"Just renamed the model"}}' -H "Content-Type: application/json" -X PUT http://server:port/v1/cars/abc-def-ghi 168 | ``` 169 | 170 | * *GET:* Use this verb for read only operations. Like Reading a single resource (GET /cars/{id}) or a collection (GET /cars) or when a search is executed (GET /cars/search?color=red&engine=1.6). *Do not put anything as part of the body payload since GET must be cacheable, use always query params* 171 | 172 | * *DELETE:* To delete a whole resource. Example: DELETE /cars/{id} 173 | 174 | ### Use Patch for exceptions 175 | 176 | It is a very special verb, useful when: 177 | 178 | * You can't design the endpoint in a REST way. Example: /bikes/{id}/active or /bikes/{id}/inactive 179 | * You want to update a resource partially 180 | * You can't ensure the idempotency of an update operation. Example: /bikes/{id}/toggle (which actives or not a bike, so it never would be idempotent) 181 | 182 | Even it is not a real consensus about when to use Patch. It is heavily used when a command needs to be implemented. Here you have a [further reading](https://stackoverflow.com/questions/28459418/use-of-put-vs-patch-methods-in-rest-api-real-life-scenarios) 183 | 184 | ### Consider the next set of Http Status codes by default 185 | 186 | * Success: 187 | * 201 - CREATED :arrow_right: Use it only when a new resource is "posted" 188 | * 200 - SUCCESS :arrow_right: The request was processed succeeded at the server 189 | 190 | * Bad Request: 191 | * 400 - BAD REQUEST :arrow_right: The server understood the request, but it failed. Use this Status when: 192 | * The request has a lack of mandatory fields 193 | * Or some fields are violating a business rule (what if I'm creating a car with only two wheels if four as the expected value for the server?) 194 | * Or any other validation failed 195 | * 401 - UNAUTHORISED :arrow_right: The requested resource/endpoint needs a previous authorisation and it was not found or expired 196 | * 403 - FORBIDDEN :arrow_right: The authorisation is ok, but it has no permissions to access to the requested resource/endpoint 197 | * 404 - NOT FOUND :arrow_right: The requested resource does not exist in the system. 198 | 199 | * Internal Server Errors: 200 | * 500 - Internal :arrow_right: Well this should not be never thrown from our server since it means something was not taken into account 201 | 202 | Again this is a default approach, sometimes could be great to return a different kind of status code (like 405 - Conflict) Always it will depend on the use case you're modelling. For further reading please check the whole list of [Http Status Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) 203 | 204 | #### POST 205 | 206 | * /cars - returns 201 if created :white_check_mark: 207 | * /cars - returns 200 if created :x: 208 | 209 | * /cars - returns 400 if a validation failed and the car could not be created ✅ 210 | * /cars - returns 403 if the requester has no permissions to create a new car ✅ 211 | 212 | #### PUT/PATCH/DELETE 213 | 214 | * /cars/abc-der-123 - returns 200 if updated ✅ 215 | 216 | * /cars/abc-der-123 - returns 404 if abc-der-123 car does not exists ✅ 217 | * /cars/abc-der-123 - returns 400 if a validation failed and the car could not be updated ✅ 218 | * /cars/abc-der-123 - returns 403 if the requester has no permissions to update/delete an existing car ✅ 219 | 220 | #### GET 221 | 222 | * /cars/abc-der-123 - returns 200 if car found ✅ 223 | * /cars/search?color=red - returns 200 if red cars found ✅ 224 | * /cars/search?color=green - returns 200 if green cars were not found ✅ 225 | 226 | * /cars/abc-der-123 - returns 404 if abc-der-123 car does not exists ✅ 227 | * /cars/abc-der-123 - returns 403 if the requester has no permissions to get an existing car ✅ 228 | * /cars - returns 403 if the requester has no permissions to retrieve the whole collection of cars ✅ 229 | * /cars/search?engine=electric - returns 403 if the requester has no permissions to search over the collection of cars ✅ 230 | 231 | 232 | ### Avoid easy guessing id's as part of your endpoint, keep your primary keys private!! 233 | 234 | As it is shown in some examples above, the id of the resource is heavily used in order to perform fetching/updating/deleting operations. So then, is not a good idea to have any kind of easy guessing id, like an incremental one. Because that id can be easily changed at the URL, and it could lead to a bad/wrong usage in order to receive non-allowed information of any other resource. 235 | 236 | * /cars/6dd7f186-1ecb-11ed-861d-0242ac120002 ✅ 237 | * This is a hard guessing id, since even if the id gets changed at URI it would be hard to know a new valid id 238 | * /cars/112 :x: 239 | * This is wrong since a potential hacker could guess the id is autoincremental, and it could easily change the URL in order to check "what if I request 113 or 114"? 240 | 241 | Finally, again don't mix up concepts. The way of modelling the API must be agnostic from data sources and so on. Don't think in the id like if it were your primary key of a table in a database. The table in the database could have its own primary key (autoincremental) but as a private one. The only thing you need to consider is to have a public way to fetch/update/delete resources. 242 | 243 | Consider to use UUID for that purpose. Here you have some readings about it: 244 | 245 | * https://www.at7.it/en/blog/rest_uuid_resource 246 | * https://stackoverflow.com/questions/31584303/rest-api-and-uuid 247 | 248 | 249 | ### Consider using pagination when heavy load of data is expected (Search endpoints) 250 | 251 | Sometime is fine to retrieve a whole collection of resources, to perform later filtering/sorting at client side. This approach is fine when you don't expect a heavy load of data. But when so much data is expected, like historic data, there is a big risk of killing the backend side, in order to receive requests that are hard to process. 252 | 253 | So then, consider to use pagination at server side. Here you have a running example at [this repo](https://github.com/geeksusma/search-endpoint-example) 254 | 255 | ### Follow the JSON Api for errors 256 | 257 | Just imagine, you need to check against the datasource if a car that is going to be created can be created, for example you need to check if there is no other model for that car exactly as the car you want to save, you want to avoid duplicities. And if finally there is some kind of duplicity, then you need to return an error back from the server. 258 | 259 | It was already mentioned, if that situation happens then we need to return a *400 Bad Request* but it doesn't bring enough information to let us know what really happened with that request. Probably we want to show an error message (that need to be translated to different languages) or probably we want to highlight a field in our UI that is the responsible for having that failure at backend side. 260 | 261 | To achieve that, we need a structure to follow to represent an error. 262 | 263 | Even there is no standard define about how to return errors from Backend side, the industry recommends following the [JSON Api approach.](https://jsonapi.org/format/#errors) 264 | 265 | Even it could be complicated, feel free to reduce/skip the amount of info to put inside the error structure. But at least try to follow that convention as much as possible. *Avoid return just a plain text with a generic message* 266 | 267 | ### Separate Write from Read Models 268 | 269 | This pattern is coming from [CQRS](https://martinfowler.com/bliki/CQRS.html) in fact, what it is suggested is just a part of CQRS. The main idea here is, there is nothing wrong in model the same concept of the API in different ways. And it is a good idea to separate the model used to fetch/collect data from the model used to write/update data. 270 | 271 | The thing is, if just have a single "car" resource modelled in just one way, probably some clients will retrieve more data than expected. Example: 272 | 273 | * GET /cars ➡️ can return a Car object, fully detailed 274 | * GET /offroads ➡️ will return also cars, since a Offroad is a type of car, but modelled as an Offroad object, which has a different set of fields/attributes 275 | 276 | Similar thing when you have a custom model for creating/updating a resource (a car) probably the data needed to create a generic car is different than the data needed to create a offroad. And even the data needed to update a whole resource probably is different than the data needed to create the resource. 277 | 278 | So then is fine to have: 279 | 280 | #### Read Models 281 | 282 | ``` 283 | { 284 | "car": { 285 | "id": "abc-def-ghi", 286 | "brand": "Volvo", 287 | "model": "V40", 288 | "engine": "v1.9 - 110cv - GAS", 289 | "pieces": [ 290 | 1, 291 | 2, 292 | 3 293 | ] 294 | } 295 | } 296 | ``` 297 | 298 | 299 | ``` 300 | { 301 | "offroad": { 302 | "id": "abc-def-ghi", 303 | "brand": "Volvo", 304 | "model": "CX90", 305 | "fourTractionWheels": true, 306 | "extras": [ 307 | 1, 308 | 2, 309 | 3 310 | ] 311 | } 312 | } 313 | ``` 314 | 315 | #### Write Models 316 | 317 | ``` 318 | { 319 | "newCar": { 320 | "id": "abc-def-ghi", 321 | "brand": "jkl-mn-opq", 322 | "model": "New Model to hit the market!" 323 | } 324 | } 325 | ``` 326 | 327 | ``` 328 | { 329 | "updatedCar": { 330 | "brand": "rst-abc-xyz", 331 | "model": "Just renamed the model" 332 | } 333 | } 334 | ``` 335 | 336 | ### Versioning best practices 337 | 338 | At least try to follow: 339 | 340 | - Enable backward compatibility 341 | - Keep the API Documentation updated to reflect new versions (or changes in the existing one) 342 | - Adapt API versioning to business requirements 343 | - Put API security considerations at the forefront 344 | 345 | Keep in mind, in the most of the cases, you'll start with the version 1 of your API, and it will never be changed. Only create a new version when a breaking change is introduced. For example: 346 | 347 | - A change in the format of the response data could break a caller 348 | - A change in types used for requesting/response data (changing an integer to a float) 349 | - Removing any part of the API 350 | 351 | Non-breaking changes, such as adding new endpoints or new response parameters, do not require a change to the major version number. 352 | 353 | --------------------------------------------------------------------------------