Building Rest API response Object based on consumers requests - api

I am building rest API & below are my end points.
EndPoint 1:
/products/{code} --> giving product inforamtion
Endpoint 2:
/products/{code}/packages --> provides packages for a given productcode
Endpoint 3:
/products/{code}/suppliers --> provides suppliers for a given product code
Endpoint 4:
/products/{code}/shelfTags --> provides shelfTags for a given product code
We have multiple down stream systems(more than 20 downstream systems) which require products & it's related information.
Note: Not all users require the nested collection information, some clients need only product information and they are good and below are the combinations and it varies by consumers
1. product info only --> **consumer 1**
2. product , packages --> **consumer 2**
3. product, suppliers, packages--> **consumer 3**
4. product, supplier, packages, shelfTags--> **consumer 4**
5. product, supplier, shelfTags --> **consumer 5**
6. product, shelfTags --> **consumer 6**
7. etc...
From above example, consumer 4 makes Http call to get product code and now has to make multiple Http calls to get packages (Endpoint 2) or suppliers (Endpoint 3) or shelfTags (Endpoint 4) etc... Is this a good design ?
Is there a way consumers can get only what they want in response on one request ? (Now is it a good design to give data needs in one request ? or it's good to ask consumers to make multiple Http calls to get nested collection ?)
Note : I cannot include all nested collection along with Products Endpoint 1 itself as it's requires huge data querying so I am planning to only provide what consumer may need, that will reduce unnecessary querying and also providing irrelevant information to few consumers who don't need that data.
Current Design:
I have below now:
Approach 1:
/products/{code}?Options = packages, Suppliers
Above would give Product details and have options query parameter based on that I can decide whether to pass Packages & supplier, shelftags etc, but here we are not filtering on resource to pass query parameter, I believe this is not a good approach as query params are only used to filter on the resources.
Approach 2:
Form a different endpoint as query parameter on the resource is for only filters if I am not wrong so looking at below option:
/products/{code}/extendedProductDetails?Options = Packages, Suppliers
In option2 extendedProductDetails is an operation rather than resource itself and I am filtering on the operation.
Can anyone provide solution on how to solve this requirement

Approach 1 vs. Approach 2
Assuming that you want to use REST, from my point of view, between the options you gave, I would go with something like Approach 2, since it is a proper collection for extended information. However, I think I'd prefer to model it such as /products-extended/{code}?options=packages,suppliers, since it defines a different collection.
Besides enhancing the readability of the API, in this way, you have the products collection and the products-extended collection: each of them can be consumed independently and with different query string filters (of course that less filtering is prone to increase complexity and latency, but in my opinion, query string parameters should be optional). If they must really not be optional and there is always the need to provide a product id and at least one nested collection, then, you can also consider designing something like products-extended/{code}/{packages,suppliers,etc}. Either way, this would "protect" your products collection.
Moreover, this would allow you to perform different operations (POST, PUT,...) to both collections, if your use case requires such.
Other approaches
Besides the other suggestions on GraphQL - would be great, yes :) -, OData or the custom types, couldn't you keep only with the individual collections? Depending on your use case, maybe you could perform parallel calls to /products/{code}/packages, /products/{code}/suppliers and so on, since you already know the product id. Perhaps, the major drawback of this design would be, for example, to create new products. However, the GET requests became super easy :)

Maybe a solution would be to use custom media types in the request header:
application/json+info-only
application/json+supplier
application/json+supplier+packages
etc.
In your controller action you would check for the selected media type and respond to the request based on them. Simply return an IActionResult and your consumer will get the data within one request.
It's very similar to your approaches but with custom extended media types you would have still one endpoint without additional parameters.

Related

RESTful Endpoint that returns single entity by two different unique parameters but not both at the same time

I want to understand what would be the best way to represent this in a RESTful way, taking in consideration that the codebase it's a very large - inherited - legacy project and I have to add a lot of new functionality on top of it.
The API Definition is built with OpenaAPI3.
Let's take in consideration the following example:
/v1/{customer}/types/{id}
But the Types collection also has a database constraint of Unique(customer, code) - customer and code being columns from the Types table.
What I need to implement now is a new endpoint that will retrieve a single entity, based on the customer path param and code path param, without having to use the ID path param.
It's a matter of reducing the number of calls, that's why I don't want to make use of the ID path param also.
One solution would be to use query params:
/v1/{customer}/types?code=123
But this will basicaly return a Singleton List so it's not that trivial and definetley not a best practice.
What would be your take on this? I know I should have the ID in the place I want that entity to be returned, but this some case I want to get resovled without having to do another call to get the ID of the entity so I can call the initial endpoint.

Work with dto's to build an API with DDD

I'm starting to work with dto's (data transfer objects) and I have some doubts about the best way to build the system architecture of the API.
Imagine a domain entity 'A', with relations to 'B', 'C' and 'D'. We have a service 'S' that return a json list with all "A's". It's correct to create an 'ADTO' in that service, fill with "BDTO's", "CDTO's" and "DDTO's"? If then we have another service "S2", and we need to return an specific set of "B's", then we need to create another tree of "B2DTO's" with "C2DTOS's", "D2DTO's"... ? Is this the correct way to do it?
I see that this way, we'll have a huge and complex tree of DTO's, with an specific DTO's for each use case.
EDIT:
I forgot the assemblers part. Is necessary to implement a different assembler for every DTO? for example, for an entity A, we have two DTO's. Can I use the same assembler or is better to have A1Assembler and A2Assembler?
Your DTOs should represent a set of data that you want your client to have. Usually, you should never 'copy' your entities into DTOs because you may have fields that you don't want to share with the world. Let's supposed that you are creating automatically a 'tracking' column with the ID of who entered that data, or say that you have a Customer entity with password fields. You don't want that to be part of your DTOs. That's why you must be EXTRA CAREFUL when using AutoMapper etc.
When you design DTOs think about what your client needs from that endpoint specifically. Sometimes DTOs may look the same and that's ok. Also, your DTOs can be as simple or as complex as needed. One crazy example, lets say that a page where you show an artist, his songs, the voting rate for those songs and some extra data.
If your use case justifies it, you may very well put all of that into a DTO. DTO all they do is carry data.
YES, your services should return DTOS (POCO).
Also, DTO is just naming convention. Don't get caught up in the "dto" suffix. A 'command' coming from a client is a DTO, but in that case you would call it AddNewCustomerCommand for example.
Makes sense?
I think you mistake what your DTO's are. You'll have 2 kind of DTO's Roughly speaking
1) they can be your domain entities, then you can return ADTO, BDTO and CDTO. But those DTO's can be fairly consistent (why would B2DTO be any different from BDTO)
If you look at what your json would look at
{
Id: 1
name: "foobar",
$type: "A",
B: [ {
name: "b-bar",
$type: "B"}]
CIds: [ 2,23, 42]
}
Here you see 2 kind of objects, some (B's) are returned in full in your DTO as subobjects. Others (like C) are turned by Id and can be queried separately. If it's S2 which implements the C query or not you don't care about.
2) When you get to an architecture like CQRS then you do get different DTO's. (projections or commands) but then you would also see this in the naming of the DTO's. Forexample are
AListOnOverviewPageDTO, AUserEditDetailDTO, etc.
Now it makes very much sense to have different DTO's since they are projections representing very different usecases. (and not a full object as is common in DDD)
Update The reason you want different DTO's are twofold. First of all it allows you to optimize each call separately. Maybe the list needs to be faster so (using CQRS) you can put the right indexes on your data so your listDTO will be returned faster. It allows you to reason about usecases more easily. Since each DTO represents 1 usecase/screen (Otherwise you get cases ok in the userlistDTO i need to populate only these 3 fields in this case ..etc.).
Furthermore you need to make sure you API is honest. NEVER EVER return a "age" field with empty data but have some other call return the same user but with another call return the same user with a real age. It makes your backend appear broken. However if i had a call to /users/list and another call to /users/1/detail it would be natural if the detail calls returned more fields about a specific user

Getting specific Backbone.js models from a collection without getting all models first

I'm new to Backbone.js. I'm intrigued by the idea that you can just supply a URL to a collection and then proceed to create, update, delete, and get models from that collection and it handle all the interaction with the API.
In the small task management sample applications and numerous demo's I've seen of this on the web, it seems that the collection.fetch() is used to pull down all models from the server then do something with them. However, more often than not, in a real application, you don't want to pull down hundreds of thousands or even millions of records by issuing a GET statement to the API.
Using the baked-in connection.sync method, how can I specify parameters to GET specific record sets? For example, I may want to GET records with a date of 2/1/2014 or GET records that owned by a specific user id.
In this question, collection.find is used to do this, but does this still pull down all records to the client first then "finds" them or does the collection.sync method know to specify arguments when doing a GET to the server?
You do use fetch, but you provide options as seen in collection.fetch([options]).
So for example to obtain the one model where id is myIDvar:
collection.fetch(
{
data: { id: myIDvar },
success: function (model, response, options) {
// do a little dance;
}
};
My offhand recollections is that find, findWhere and where would invoke all models being downloaded and then the filtering taking place on the client. I believe with fetch the filtering takes places on the server side.
You can implement some kind of pagination on server side and update your collection with limited number of records. In this case all your data will be up to date with backend.
You can do it by overriding fetch method with you own implementaion, or specify params
For example:
collection.fetch({data: {page: 3})
You can also use find where method here
collection.findWhere(attributes)

Foursquare venues/search doesn't return full categories list

For example in this request I'm looking for venues with categoryId=4bf58dd8d48988d1e8941735 (Baseball Field): https://developer.foursquare.com/docs/explore#req=venues/search%3Fll%3D40.7,-74%26categoryId%3D4bf58dd8d48988d1e8941735
In the result each venue has only one (primary) category. Is it possible in venuess/search request to return full venue's categories list?
Unfortunately, no.
This is actually by design, for performance considerations. In contexts where we return many JSON items (e.g. venues/search), we only provide the "compact" JSON to keep overall response-size down, since it's a significant contributor to the cost of serving and handling the request. venues/search in particular is one of our most highly trafficked endpoints.
If you want more detail about a particular result in venues/search, you should query the venue-detail endpoint (/venues/[VENUE-ID]), which will return the full list of categories.

Searching Authorize.net CIM Records

Has anyone come up with an elegant way to search data stored on Authorize.net's Customer Information Manager (CIM)?
Based on their XML Guide there doesn't appear to be any search capabilities at all. That's a huge short-coming.
As I understand it, the selling point for CIM is that the merchant doesn't need to store any customer information. They merely store a unique identifier for each and retrieve the data as needed. This may be great from a PCI Compliance perspective, but it's horrible from a flexibility standpoint.
A simple search like "Show me all orders from Texas" suddenly becomes very complicated.
How are the rest of you handling this problem?
The short answer is, you're correct: There is no API support for searching CIM records. And due to the way it is structured, there is no easy way to use CIM alone for searching all records.
To search them in the manner you describe:
Use getCustomerProfileIdsRequest to get all the customer profile IDs you have stored.
For each of the CustomerProfileIds returned by that request, use getCustomerProfileRequest to get the specific record for that client.
Examine each record at that time, looking for the criterion you want, storing the pertinent records in some other structure; a class, a multi-dimensional array, an ADO DataTable, whatever.
Yes, that's onerous. But it is literally the only way to proceed.
The previously mentioned reporting API applies only to transactions, not the Customer Information Manager.
Note that you can collect the kind of data you want at the time of recording a transaction, and as long as you don't make it personally identifiable, you can store it locally.
For example, you could run a request for all your CIM customer profile records, and store the state each customer is from in a local database.
If all you store is the state, then you can work with those records, because nothing ties the state to a specific customer record. Going forward, you could write logic to update the local state record store at the same time customer profile records are created / updated, too.
I realize this probably isn't what you wanted to hear, but them's the breaks.
This is likely to be VERY slow and inefficient. But here is one method. Request an array of all the customer Id's, and then check each one for the field you want... in my case I wanted a search-by-email function in PHP:
$cimData = new AuthorizeNetCIM;
$profileIds = $cimData->getCustomerProfileIds();
$profileIds = $cimData->getCustomerProfileIds();
$array = $profileIds->xpath('ids');
$authnet_cid = null;
/*
this seems ridiculously inefficient...
gotta be a better way to lookup a customer based on email
*/
foreach ( $array[0]->numericString as $ids ) { // put all the id's into an array
$response = $cimData->getCustomerProfile($ids); //search an individual id for a match
//put the kettle on
if ($response->xml->profile->email == $email) {
$authnet_cid = $ids;
$oldCustomerProfile = $response->xml->profile;
}
}
// now that the tea is ready, cream, sugar, biscuits, you might have your search result!
CIM's primary purpose is to take PCI compliance issues out of your hands by allowing you to store customer data, including credit cards, on their server and then access them using only a unique ID. If you want to do reporting you will need to keep track of that kind of information yourself. Since there's no PCI compliance issues with storing customer addresses, etc, it's realistic to do this yourself. Basically, this is the kind of stuff that needs to get flushed out during the design phase of the project.
They do have a new reporting API which may offer you this functionality. If it does not it's very possible it will be offered in the near future as Authnet is currently actively rolling out lots of new features to their APIs.