How we build APIs @ Huddle

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

How we build APIs @ Huddle

1fd6a509e0fdaddf8df7270857e7dd92?s=128

yiannis

September 17, 2014
Tweet

Transcript

  1. 1.
  2. 3.

    /agenda • Design • Hypermedia • Media types • Versioning

    • Testing • APIs in overall architecture
  3. 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
  4. 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}
  5. 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>
  6. 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!
  7. 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
  8. 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
  9. 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
  10. 12.

    /testing • Unit tests • Handler level • C# using

    MSpec • Acceptance tests • Outside-in, mostly happy path • Python using requests, contexts
  11. 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”); }
  12. 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
  13. 15.

    /architecture • Hexagonal architecture (Alistair Cockburn) • API as a

    port • Framework as an adapter • CQRS • ViewBuilders and Queries for reads • Commands for everything else
  14. 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 }; }
  15. 18.

    /write-side readonly commandDispatcher; public OperationalResult Delete(int userId){ var deleteCommand =

    new DeleteUserCommand(){ UserId = userId; }; commandDispatcher.Send(deleteCommand); return new OperationResult.NoContent(); }
  16. 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
  17. 21.