Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How we build APIs @ Huddle

1fd6a509e0fdaddf8df7270857e7dd92?s=47 yiannis
September 17, 2014
140

How we build APIs @ Huddle

1fd6a509e0fdaddf8df7270857e7dd92?s=128

yiannis

September 17, 2014
Tweet

Transcript

  1. None
  2. Yiannis Triantafyllopoulos How we build APIs @ Huddle

  3. /agenda • Design • Hypermedia • Media types • Versioning

    • Testing • APIs in overall architecture
  4. /design • Big design up front is dumb, no design

    up front is dumber • Documentation-driven design • Endpoints, verbs, status codes and examples • Feedback from everyone • Consult and validate during code review, arch review and manual testing • Use Wiki or Markdown because why not? • API != CRUD • Follow use cases, not database tables • Eat your own dog food
  5. /hypermedia • One and only one entry point • Follow

    links from there • Nice URIs are cute but conceptually treat them as opaque strings • Avoid URI templates • /resource/{resourceId}/subresource/{subresourceId}
  6. /hypermedia-entry-point GET /entry 200 OK { „links‟:[ {„rel‟:‟self‟,„href‟:‟…/users/42‟}, {„rel‟:‟avatar‟,„type‟:‟image/jpg‟, „href‟:‟…‟

    {„rel‟:‟notifications-received‟,‟href‟:‟…‟} „membership‟:{ … } }
  7. /media-types • Contract between client and server on how to

    process the response • No application specifics here • Choice of generic vs. custom (link) • HAL • JSON-LD • JSON API • Collection+JSON • <your custom media type here>
  8. /media-types • application/vnd.huddle.data+json • links • Represents an action that

    can be performed against a resource • actors • Represents a user who has performed an action against a resource e.g. owner, manager, assignee • Opt for a generic one if you can • If you have to define your own, make sure you separate it from your domain!
  9. /media-types • “edit” link relation Example GET /resource/123 200 OK

    { „self‟:‟/resources/123‟, „edit‟:‟/resources/123/edit‟ } GET /resources/123/edit 200 OK { „name‟:‟foo‟ } PUT /resources/123/edit { „name‟:‟bar‟ } 204 No Content
  10. /versioning • URI • GET /api/v2/resources/123 • URIs identify resources

    and therefore should never change • Headers • Custom request header • X-Api-Version • Custom media type • application/vnd.github.v3+json • Custom media type per resource • application/vnd.github.user.v3+json • Clients need to set the correct headers for every call
  11. /versioning • Adding new links or resources doesn‟t break existing

    clients • Removing or renaming links or resources breaks existing clients • In practice though that doesn‟t happen very frequently • Make breaking changes early on in development so that you don‟t have to make them in production
  12. /testing • Unit tests • Handler level • C# using

    MSpec • Acceptance tests • Outside-in, mostly happy path • Python using requests, contexts
  13. /unit-test public class UserExists { Establish context => { user

    = new UserBuilder().WithName(“bob”).Persist(); handler = new UserHandler(new SqlLiteRepository(), new Mock<CommandDispatcher>()); }; Because of => { response = handler.Get(user.Id); }; It has_successful_response => response.StatusCode.ShouldEqual(200); It has_user_resource => response.View.Name.ShouldEqual(“bob”); }
  14. /acceptance-test @context class WhenGettingAUser(PersistedResource): def given_a_user(self): self.name = „bob‟ self.user

    = self.persistUser(name=self.name) def when_getting_the_user(self): self.retrievedUser = self.api.getResource(self.user.selfUri, User.fromJson) def then_the_user_is_returned(self): assert self.retrievedUser.name == self.name
  15. /architecture • Hexagonal architecture (Alistair Cockburn) • API as a

    port • Framework as an adapter • CQRS • ViewBuilders and Queries for reads • Commands for everything else
  16. /ports-and-adapters

  17. /read-side readonly viewBuilder; readonly userQuery; public OperationalResult Get(int userId){ var

    viewModel = userQuery.Execute(userId); if(viewModel == null){ return new OperationResult.NotFound(); } var userView = viewBuilder.Render(viewModel); return new OperationResult.OK(){ Resource = userView }; }
  18. /write-side readonly commandDispatcher; public OperationalResult Delete(int userId){ var deleteCommand =

    new DeleteUserCommand(){ UserId = userId; }; commandDispatcher.Send(deleteCommand); return new OperationResult.NoContent(); }
  19. /the-end • APIs are use cases, design them for clients

    • Single entry point, follow links from there • Avoid versioning if you can • Test your port, not your stack
  20. /thank-you • We‟re hiring! • https://talentcommunity.huddle.com/careers

  21. None