0

It is not clear to me that if I have a micro service that is in place to provide some derived data how the rest api should be designed for this. For instance :-

If I have a customer and I want to access the customer I would define the API as:

/customer/1234

this would return everything we know about the customer

however if I want to provide a microservice that simply tells me if the customer was previously known to the system with another account number what do I do. I want this logic to be in the microservice but how do I define the API

customer/1234/previouslyKnow

customerPreviouslyKnown/1234

Both don't seem correct. In the first case it implies

customer/1234

could be used to get all the customer information but the microservice doesn't offer this.

Confused!

Adding some extra details for clarification.

I suppose my issue is, I don't really want a massive service which handles everything customer related. It would be better if there were lighter weight services that handles customer orders, customer info, customer history, customer status (live, lost, dead....).

It strikes me all of these would start with

/customer/XXXX

so would all the services be expected to provide a customer object back if only customer/XXXX was given with no extra in the path such as /orders

Also some of the data as mentioned isn't actually persisted anywhere it is derived and I want the logic of this hidden in a service and not in the calling code. So how is this requested and returned.

Steve Webb
  • 11
  • 4

3 Answers3

1

Doing microservices doesn't mean to have a separate artifact for each method. The rules of coupling and cohesion also apply to the microservices world. So if you can query several data all related to a customer, the related resources should probably belong to the same service.

So your resource would be /customers/{id}/previous-customer-numbers whereas /customers (plural!) is the list of customers, /customers/{id} is a single customer and /customers/{id}/previous-customer-numbers the list of customer numbers the customer previously had.

Try to think in resources, not operations. So returning the list of previously used customer numbers is better than returning just a boolean value. /customer/{id}/previous-accounts would be even better, I think...

Back to topic: If the value of previous-accounts is directly derived from the same data, i.e. you don't need to query a second database, etc. I would even recommend just adding the value to the customer representation:

{
    "id": "1234",
    "firstName": "John",
    "lastName": "Doe",
    "previouslyKnown": true,
    "previousAccounts": [
        {
            "id": "987",
            ...
        }
    ]
}

Whether the data is stored or derived shouldn't matter so the service client to it should not be visible on the boundary.

Adding another resource or even another service is unnecessary complexity and complexity kills you in the long run.

You mention other examples:

customer orders, customer info, customer history, customer status (live, lost, dead....)

Orders is clearly different from customer data so it should reside in a separate service. An order typically also has an order id which is globally unique. So there is the resource /orders/{orderId}. Retrieving orders by customer id is also possible:

/orders;customer={customerId}

which reads give me the list of orders for which the customer is identified by the given customer id.

These parameters which filter a list-like rest resource are called matrix parameters. You can also use a query parameter: /orders?customer={customerId} This is also quite common but a matrix parameter has the advantage that it clearly belongs to a specific part of the URL. Consider the following:

/orders;customer=1234/notifications

This would return the list of notifications belonging to the orders of the customer with the id 1234.

With a query parameter it would look like this:

/orders/notifications?customer=1234

It is not clear from the URL that the orders are filtered and not the notifications.

The drawback is that framework support for matrix parameters is varying. Some support them, some don't.

I'd like matrix parameters best here but a query parameter is OK, too.

Going back to your list:

customer orders, customer info, customer history, customer status (live, lost, dead....)

Customer info and customer status most likely belong to the same service (customer core data or the like) or even the same resource. Customer history can also go there. I would place it there as long as there isn't a reason to think of it separately. Maybe customer history is such a complicated domain (and it surely can be) that it's worth a separate service: /customer-history/{id} or maybe just /customer/{id}.

It's no problem that different services use the same paths for providing different information about one customer. They are different services and they have different endpoints so there is no collision whatsoever. Ideally you even have a DNS alias pointing to the corresponding service:

https://customer-core-data.service.lan/customers/1234
https://customer-history.service.lan/customers/1234
R2C2
  • 728
  • 6
  • 19
0

I'm not sure if I really understand your question. However, let me show how you can check if a certain resource exist in your server.


Consider the server provides a URL that locates a certain resource (in this situation, the URL locates a customer with the identifier 1): http://example.org/api/customers/1.

When a client perform a GET request to this URL, the client can expect the following results (there may be other situation, like authentication/authorization problems, but let's keep it simple):

  • If a customer with the identifier 1 exists, the client is supposed to receive a response with the status code 200 and a representation of the resource (for example, a JSON or XML representing the customer) in the response payload.
  • If the customer with the identifier 1 do not exist, the client is supposed to receive a response with the status code 404.

To check whether a resource exists or not, the client doesn't need the resource representation (the JSON or XML that represents the customer). What's relevant here is the status code: 200 when the resource exists and 404 when the resource do not exist. Besides GET requests, the URL that locates a customer (http://example.org/api/customers/1) could also handle HEAD requests. The HEAD method is identical to the GET method, but the server won't send the resource representation in HEAD requests. Hence, it's useful to check whether a resource exists or not.

See more details regarding the HEAD method:

4.3.2. HEAD

The HEAD method is identical to GET except that the server MUST NOT send a message body in the response (i.e., the response terminates at the end of the header section). The server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request had been a GET, except that the payload header fields MAY be omitted. This method can be used for obtaining metadata about the selected representation without transferring the representation data and is often used for testing hypertext links for validity, accessibility, and recent modification. [...]


If the difference between resource and resource representation is not clear, please check this answer.

Community
  • 1
  • 1
cassiomolin
  • 124,154
  • 35
  • 280
  • 359
0

One thing I want to add to the already great answers is: URLS design doesn't really matter that much if you do REST correctly.

One of the important tenets of REST is that urls are discovered. A client that has the customers's information already, and wants to find out what the "previously known" information, should just be able to discover that url on the main customer resource. If it links from there to the "previously known" information, it doesn't matter if the url is on a different domain, path, or even protocol.

So if you application naturally makes more sense if "previouslyKnown" is on a separate base path, then maybe you should just go for that.

Evert
  • 93,428
  • 18
  • 118
  • 189