Public API contracts

To serve HTTP requests, we need more than just a controller. The controller needs to accept strongly typed requests, and the collection of those requests will be our public API and models for those requests will be our contracts. Let's prepare a place where we'll put these models and create a folder called Contracts, in the application project.

Contracts are data-transfer objects (DTOs) and plain-old C# objects (POCOs). It means that have no logic, only contain primitive types and do not require any tricks to be serialized and deserialized. Practically, you can add complex types to your DTOs just because some complex types are used in many contracts. An example of such type could be the Address type.

Imagine, we have the following contract class:

public class UpdateCustomerAddressDetails
{
public string BillingStreet { get; set; }
public string BillingCity { get; set; }
public string BillingPostalCode { get; set; }
public string BillingCountry { get; set; }
public string DelvieryStreet { get; set; }
public string DelvieryCity { get; set; }
public string DelvieryPostalCode { get; set; }
public string DelvieryCountry { get; set; }
}

It is very convenient to use the complex type instead of listing all properties for two addresses separately. After we add a new type called Address, our contract will be much more compact:

public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}

public class UpdateCustomerAddressDetails
{
public Address BillingAddress { get; set; }
public Address DelvieryAddress { get; set; }
}

Be aware that complex types add compatibility issues since when you change such type, all contracts that use it will also change and this change will be implicit. Since you have no internal consumers of this contract, you will not be able to see if clients that use this contract will be affected. Such information can only be obtained by tests.

Speaking about changes, remember that everything you publish outside of your development machine, is considered public. Public API is something that anyone, who has appropriate permissions, can use. Effectively it means that you are not in control of who is using your API anymore. Therefore, any change in the published API can potentially break other parts of the system or other systems. Changes in public contracts need to be crafted with care since there are both non-breaking changes and breaking changes.

Some changes in POCO types are considered non-breaking. These are changes like:

  • Changing property type so any value of the type that was used before can be serialized to the new type. For example, we can change a property from being int to string, and it will be compatible.
  • Adding a new property is also considered as a non-breaking change. It's because when we try to deserialize an XML or JSON object that does not have this new property because the sender hasn't updated their contracts yet, most popular serializers will accept it and will use a default value if the value is not supplied.

Our software evolves, and of course, it is not always possible to make non-breaking changes. It means that we should be prepared to make breaking changes. We already discussed that public API is something that is shared by default as soon as it is made public. Therefore, we need to ensure that when we make a breaking change, everyone who is using the old API, will not get exceptions and will be able to work as before, at least for some time. It is done by API versioning. You probably have encountered different API versions for popular services like GitHub or Twitter. For example, at the moment I am writing this, Twitter API documentation tells me to use this call to get the tweets timeline: GET https://api.twitter.com/1.1/statuses/home_timeline.json. As you can see there, they have 1.1 as part of the URI, and it is their current stable API version. We could assume that they also have other versions and some of those older versions might still be operational and in use. So, in our API we will also use versioned contracts although we don't expect many changes in the beginning.

We already know what operations we can do with our domain, so we can add some contracts to call these operations from the outside world. Let's create a file where we put our first contracts. We already have the Contracts folder, so we can create a new C# class file called ClassifiedAds.cs in this folder. After the file is in place, we can add our first contract there:

using System;

namespace Marketplace.Contracts
{
public static class ClassifiedAds
{
public static class V1
{
public class Create
{
public Guid Id { get; set; }
public Guid OwnerId { get; set; }
}
}
}
}

Here we use nested static classes ClassifiedAds and V1 as a replacement for namespaces, so we can have more versions in one file when necessary.

What we got here is a command. I first mentioned commands in Chapter 1 when we discussed CQRS. Commands allow users and other systems to execute actions in our domain model. When a command is successfully processed, the domain model state changes and new domain events are emitted. Now, when we have one command implemented in code, we need to accept this command coming from the outside world, and we'll use an HTTP endpoint for this.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.45.5