Free your API. Misconceptions are holding you back!

Поділитися
Вставка
  • Опубліковано 16 жов 2024

КОМЕНТАРІ • 51

  • @titusfx
    @titusfx Рік тому +10

    🎯 Key Takeaways for quick navigation:
    01:07 🧐 Custom HTTP headers don't need an "X-" prefix, as it was deprecated over a decade ago. Don't follow outdated practices just because they seem like industry standards.
    03:12 🚀 You don't have to treat your HTTP API as strictly CRUD over resources. Consider being explicit about the actions users need to perform, even if they don't fit CRUD operations.
    05:20 🔄 Rethink API versioning; it might be unnecessary. Avoid putting the burden of handling breaking changes on consumers and use hypermedia to evolve your API without versioning the entire thing.
    07:54 🤖 Don't blindly follow industry trends or standards. Evaluate what makes sense for your specific context and system rather than just adopting practices because others are doing it.
    08:38 📚 Join a community to discuss software architecture and design if you're interested in these topics, and always seek to learn and improve your understanding of best practices.

  • @georgemdonnelly
    @georgemdonnelly Рік тому +3

    avoided the REST dilemma, well played.

  • @michaeledwardharris
    @michaeledwardharris Рік тому

    I'm enjoying your channel. I'm honestly not totally on board with some of your opinions, but you're clearly an expert, and your videos are extremely bingeable. I'm learning things! Thanks for making these.

  • @KA-wf6rg
    @KA-wf6rg Рік тому

    "Does it even make sense anymore?" ... that one question alone freed my mind.

  • @lorcaranr
    @lorcaranr Рік тому +4

    How does the consumer of the API handle changes?
    If you make a breaking change via hypermedia. The consumer still needs to react to that change.

  • @jenswurm
    @jenswurm Рік тому

    One misconception I've often seen is that http 404 is treated as "the resource does not exist" by the client.
    In reality the resource not existing is just one out of multiple possible things that can cause a 404.
    Effectively (based on the status code alone) the client must only treat it as, "the resource is unavailable, it may or may not exist and this may or may not be only temporary."

  • @vasilisplavos
    @vasilisplavos 2 місяці тому

    The first and second points make totally sense to me! But I am not sure about the third one...
    I mean, your advice is like: Increase the complexity of the APIs, make them more unfriendly, to avoid having breaking changes every 2-4 years, because usually this is the time that the versioning is changing in the real world!
    On the other hand, having the versioning up to the URL keeps both sides safe about the API.
    Idk man, I would like to hear your thoughts on this

    • @CodeOpinion
      @CodeOpinion  2 місяці тому +1

      I've never really understood the "hypermedia adds complexity" take. From a consumers perspective, you're giving it instructions at runtime so it actually has to understand less at design time (when you're building the client). Everyone has different experiences, so our views/opinions are based on that. I have an integration with a 3rd party that has multiple versions and the amount of work we have to move those integrations isn't trivial. I have other integrations that have never versioned and always made everything backwards compatible. The difference in both of those situations is one is putting burden on the consumer and the other isn't.

  • @georgehelyar
    @georgehelyar Рік тому +2

    IMHO you should always version your APIs, but you should also try to avoid making breaking changes.
    If you have v1 in your route and you never need it, that's fine. If you don't version and then you do need it then it's a pain. After an API has existed for a long time you end up with a lot of deprecated code that you can't remove, which is just noise to any consumer, and you end up with a lot of tech debt where you wish you did something differently years ago and now you're stuck with it.
    Links in the response can help with this but they don't solve the whole problem.

  • @GabrielSoldani
    @GabrielSoldani Рік тому +18

    Good content, but I disagree on the last point. Absolutely version your API. Even if you’re using hypermedia, requirements change and the hypermedia structure you picked may change. But don’t force your consumers to change to a new version. This is very straightforward if you’re using absolutely no logic in your controllers and religiously using DTOs for in the API layer: your domain entities can cleanly map to and from previous versions of the DTOs and the application itself doesn’t need to know or care about API versions.

    • @CodeOpinion
      @CodeOpinion  Рік тому +7

      Creating different routes to add new functionality, depending how you look at it, is versioning. However, you're not forcing consumers to upgrade to an entire new API but rather specific new routes that provide new functionality. Deprecating old routes is still a thing. The point is not mass versioning your entire API for one breaking change to a specific route.

    • @RyanBoyles-work
      @RyanBoyles-work Рік тому +2

      @@CodeOpinion Everything you've mentioned around versioning is really just expanding capabilities and functionality. But if we go down the no versioning route, how would you handle real breaking changes? i.e. maybe removing properties because they are replaced by a different set?

    • @eng3d
      @eng3d Рік тому +1

      ​​​@@CodeOpiniontwo versions means x2 support, x2 vulnerabilities. So i agree, create a new method and keep both if not simply discontinue the old method

    • @kabal911
      @kabal911 Рік тому

      As with everything, it depends
      A vast majority of the APIs that we build are consumed by the same team and the “same” application. E.g line of business apps.
      In these cases, it makes little sense to support versioning out the box, because in most cases, breaking changes are acceptable

    • @GabrielSoldani
      @GabrielSoldani Рік тому +2

      @@kabal911I think these cases are quite rare, though. Unless you have maintenance windows where everything is down, there’s a period where there can be a version mismatch between API provider and consumer. Unless you’re doing an atomic deploy of your consumer and your service, but then perhaps REST isn’t the best choice?
      Either way, even if planned downtimes are acceptable I’d rather deal with versioning than having to care about planning downtimes.

  • @drewjaqua2905
    @drewjaqua2905 Рік тому +1

    I share your hatred of REST dogma. I have yet to see a job posting with the experience requirement "RESTful API development" that means anything other than "JSON API over HTTP that's generally CRUD" 😂
    Thanks for making these.

    • @andrewshirley9240
      @andrewshirley9240 Рік тому

      The funny thing is REST is *basically* just a naming convention over 1:1 resource mapping. The distinction is that "resource" doesn't always have to mean "a single table in the database" but when a company legitimately tries to follow REST, that's what ends up happening. But yeah, the vast majority of the time, companies don't even implement *that,* they just have a complete hodgepodge of route naming and it's not RESTful in the least. My current job's "REST"ful API has an endpoint named "GetJsonSchemaOverContext2" lol, where a RESTful convention would absolutely be something like "[Get] /v2/schema/json" (note: json is a real type in this context and not just the content type of the response). And I don't think REST has problems with extension actions, so something like "[Put] /order/cancel" is completely fine under REST. The important part is that the start of the route, "/order/" cleanly identifies the resource you're working with, and that the HttpVerb cleanly identifies what kind of side effects to expect from the action.
      The biggest stumbling block I've seen so far is if you have a search with complex parameters that are more suited to a JSON Body than query parameters, and while you *can* send a request body with a [Get] it's certainly its own break of convention to do so, and can cause issues with certain frameworks, so people [Post] those searches instead.

  • @francoistaljaard8322
    @francoistaljaard8322 Рік тому +1

    Rest, 15 years I still need to see one. Many levels 1 or 2, but I have never seen a rest service. JSON api, nothing wring with it

  • @luc9volts
    @luc9volts Рік тому +1

    Awesome! I always felt kind of guilty of not treat my API's always as CRUD 😅

  • @kevinkohut5096
    @kevinkohut5096 Рік тому

    Regarding custom headers, just because using "x" is no longer required doesn't mean it shouldn't be used. I like knowing instantly that it is custom when the x is there.

    • @CodeOpinion
      @CodeOpinion  Рік тому

      Sure, you absolutely can do it. The point being is that people think you have to.

  • @andreipacurariu2013
    @andreipacurariu2013 Рік тому +1

    Great video as usual! Of course, I'm focusing my comment on my only slight point of contention which has to do with the "objects as resources" and CRUD operations chapter in your video. This doesn't take away from the entirety of the video which is amazing as per usual. But, acknowledging this, I don't see much value commenting on the things I agree with and I think it's more interesting and productive to discuss the point of contention. I don't disagree with it this point entirely either (which is don't get stuck trying to force your API into a resource-based approach), but I want to offer some nuance for whom it may concern.
    Indeed, it's not a good practice to have a 1:1 mapping between the resources you're exposing through your API and your domain model. However, I'm also not a fan of exposing operations as part of the route. This position is philosophical, on the one hand, based on the idea that HTTP is defined around resources and the uniform interface concept from REST is a very sound idea. However, there's another argument for not exposing operations in your routes and sticking with resources (with the understanding that these don't have to, and often shouldn't, map to your entities). Specifically, the API modelling exercise in trying to figure out what would be the resources you're planning to expose often provides insights into the domain model itself and many times leads to a much more intention-revealing API than could be achieved by having operations in routes. Having operations in routes is such a low-effort approach that it can become lazy and might lead to skipping a deliberate and purposeful modelling exercise because that's much harder.
    Concrete example: Assume I have a system which manages employee information in an organization. An employee has a bunch of information (id, firstName, lastName, etc.) but they also have a manager. Now, the manager is a domain concept but may not be a domain entity (depending on the specific domain and business rules it may be the case that there's no behavior associated to require a Manager aggregate route or entity). In such a case, in practical terms, the manager domain concept is simply implemented as a managerId property on the Employee entity. However, despite the technical implementation lacking a Manager entity, the manager remains a domain concept. So, if we want to expose an operation for updating an employee's manager through our API, we have two options:
    1. Operation-based (or verb-based): PUT /employees/123/update-manager. In this case the new manager id is provided as an integer in the body of the request. Simple, trivial, low-effort API design with no modelling effort and no insights into the fact that despite no Manager entity, the manager concept is still an important domain concept. However, notice already the arbitrariness of me having chosen PUT as the HTTP method of choice where a case could just as easily be made that POST would have been the better choice. And this is part of the issue with the operation-based approach because you're essentially, realizing or not, building a new protocol on top of HTTP and merely using HTTP as a tunnel for your own protocol. At this point, you might as well use POST for all mutating operations because the HTTP verb doesn't matter since the operation is an imperative verb in the route.
    2. Resource-based (or noun-based): PUT /employees/123/manager/234. In this case the new manager id is provided as an integer in the route itself. So what's the advantage over the operation-based approach and why does this lead to more domain insights and and a more intention-revealing API? Because, for one, this approach makes the manager concept a first-class citizen of our ubiquitous language up to and including our API definition. And, secondly, because we can treat the manager as a first-class concept despite not relying on a Manager entity. For instance, the following API route become self-explanatory and perfectly intention revealing:
    GET /employees/123/manager
    An alternative approach would have been to expose these routes instead (again, perfectly domain-aligned and intention revealing despite the technical implementation lacking a Manager entity):
    PUT /manager/123/employees/456
    GET /manager/123/employees
    This is such a trivial API that the power of this approach might not be clearly visible, but consider a more complex case where we're dealing with entity statuses such as a project planning system where projects can be approved. If we treat the concept of a project being approved as a domain concept, despite not having a backing entity, instead of an operation-based approach we can treat approvals as a resource. Something like this:
    PUT /approvals/123 -> moves the project 123 into the "approved projects collection" which is a virtual resource, not backed by an entity, yet expressing a domain concept and intent
    DELETE /approvals/123 -> removes the project 123 from the "approved projects collection" thus the project is no longer approved.
    The following more direct and alternative would also work PUT /projects/123/approved. However, we still can't completely get rid of the virtual "approved projects collection" resource for the following use cases
    GET /approvals -> which returns the list of approved projects. Of course this supports query parameters to filter by date, take, skip, etc.
    GET /pending -> this manifests another domain concept referring to the fact that in our domain a project can be pending approval. This route would very clearly manifest the intent of retrieving all such projects.
    In short, we're using HTTP verbs to retrieve and mutate resources in a pretty CRUD fashion, however, these resources don't map to entities necessarily and therefore we're not employing a CRUD interaction with our domain. For example, assuming there's a project portfolio domain concept in our domain, backed by a Portfolio entity, and there are behaviors and business rules surrounding approving a project the operation PUT/approvals/123 (or PUT /projects/123/approved ) despite being CRUD on the surface and driven by HTTP verbs, behind the scenes would do something like:
    var portfolio = portfolioRepo.GetPortfolio();
    portfolio.ApproveProject(123);
    Now that I think about this in real-time, perhaps we have multiple portfolios, so the following routes would be even better:
    POST /portfolios
    GET /portfolios
    GET /portfolios/123
    PUT /portfolios/123
    DELETE /portfolios/123
    PUT /portfolios/123/approved-projects/456
    GET /portfolios/123/approved-projects
    GET /portfolios/123/approved-projects/456 -> This is interesting because it provides insights that the Approval is a domain entity itself with an approved date, approver and approved reason
    PUT /portfolios/123/approved-projects/456 -> So then we can modify things about the approval itself
    DELETE /portfolios/123/approved-projects/456
    GET /portfolios/123/pending-projects
    I guess it's clear where this is going. Had I started with operations such as POST /portfolios/123/approve-project then I might have missed that, hey, an approval is an entity with an id, approvalDate, approverId and approvedReason. Then I would have an inchoesive API that combines operation-based routes (to approve a project) with resource-based routes (to manage the approvals).
    Note that I'm not suggesting this is a good design for this specific domain, instead I'm using this half-thought imaginary system as an example to prove a point.
    In closing, I'm not saying that it is always necessary or superior to go the resource-based approach over the more imperative operation-based approach but I'm suggesting that we shouldn't forego with the resource-based approach too soon either because, regardless of the path we end up choosing, spending some effort trying to design within the constraints of what the resource-based approach allows can help improve our domain insights, at least, and even the improve the intention-revealing of our API, at best. I hope this makes some sense.

  • @kumailn7662
    @kumailn7662 9 місяців тому

    Except for the HEADER the X factor part, I don't think industry has any misconception of using when to use what HTTP verb, on API versioning again versioning in URI or via Header again is the good practice, robust and scalable way. (my opinion)

  • @nasragiel
    @nasragiel Рік тому

    How does the hypermedia approach (passing the action URLs with the response) work well with eventual consistency? If my process takes just a little longer than the first request to my next action, then the client will receive a 404 because the new resource does not yet exists. So what are strategies for this scenario? How do you handle that?

  • @crizo23
    @crizo23 Рік тому

    Interesting idea. Might just be me but in your example where you’re adding to the links array, I’m not sure how this would ever be a breaking change at all? In my experience, the breaking changes are in the request/ response payloads. IMO depending on links/routes from the server rather than being strictly defined in the API is only the tip of the iceberg when it comes to API changes.
    Said another way, adding a new “search” route to the existing versioned API is what I would do, no need to mess with links. But I understand that you’re just trying to show an example

  • @ericblankenburg
    @ericblankenburg Рік тому +2

    I got tired of REST-based services a long time ago. I design my API's more like RPC's calls. Everything uses a POST. Example names look like: "cancel-order", "allocate-inventory", and "get-pick-list".

  • @jdrab
    @jdrab Рік тому +3

    I don't see versioning an API as a burden on the consumer/customer. I see it as guarantee, that i won't break their app/flow. All it takes is one incompatible feature when you realize you need api versioning. IMHO it's almost guaranteed it will come sooner than later. As soon as your api is public, you can not to afford not to use api versioning.

  • @eugene3685
    @eugene3685 Рік тому

    It seems that X- just convenient way to distinguish standart and custom headers by quick look.

    • @CodeOpinion
      @CodeOpinion  Рік тому

      Ya it can be for sure. But people assume that's the "correct way", when it's not.

  • @hereallyfast
    @hereallyfast Рік тому

    You always have at least one hot take 😂

  • @vupham-i2v
    @vupham-i2v Рік тому

    could somebody re-explain me what he said about why we don't need api versioning, my English listening is bad, thank you so much

    • @bertolimauro
      @bertolimauro Рік тому +1

      You can enable subtitles using the "cc" overlay button in the player and read the text

  • @Qwasieee_P
    @Qwasieee_P Рік тому

    I saw a few tutorials where they connect APIs to CRUD and it made me confused, so I procrastinated on learning API. So much time was wasted 😢

  • @yassinebouchoucha
    @yassinebouchoucha Рік тому +3

    @6:00 I always knew it that API versioning is a bad idea (or at least not as could as it sounds like!). I never done it either, always extends new routes/paths since will never run out of English word !

    • @CodeOpinion
      @CodeOpinion  Рік тому

      I wouldn't say it's a bad idea, I just wouldn't jump to it immediately by doing something like defining it in the URI/header/etc.

  • @tomazkoritnik4072
    @tomazkoritnik4072 Рік тому

    This is why I use JsonRPC approach instead of REST. Action-based APIs are much more natural to use and I always ask myself, when I hear about REST is "why?". Really, why REST? JsonRPC can also contain hypermedia and is so much simpler to use. Why struggle to map HTTP methods to API actions? Regarding versioning, I would version actions in JsonRPC, not the whole API. Unless the whole or big part of the API changes.

  • @rookiesmarts
    @rookiesmarts Рік тому

    Very nice 👌

  • @chimchim2_
    @chimchim2_ Рік тому

    Good stuff

  • @justinng872
    @justinng872 Рік тому +1

    This channel really likes to go for controversial topics to get engagements

  • @majormartintibor
    @majormartintibor Рік тому +4

    wtf is that shirt. No workout today?

  • @awmy3109
    @awmy3109 Рік тому

    99% of the time, best practices makes sense in any context. Infact, you hardly go wrong following best practices.
    The issue is most devs don't follow best practices, think they are smarter, create their custom implementation that doesn't consider several things and introduce lots of security holes, bugs and create unmaintainable mess too.

    • @CodeOpinion
      @CodeOpinion  Рік тому +6

      Best practices require context, without it, your applying something blindly. Best practices should be called "maybe a good idea in this context, here are the trade-offs".

    • @awmy3109
      @awmy3109 Рік тому

      @@CodeOpinion Well, am yet to find that "context" where doing something right isn't the best option.

    • @CodeOpinion
      @CodeOpinion  Рік тому +1

      @@awmy3109 Example, with EDA/messaging, let's say you need consistency between state changes and publishing events a broker. "Best Practice" is using the outbox pattern. There are trade-offs with everything. The trade-off with that is you're going to increase load on your primary database. The alternative is using a fallback when you fail to publish, to either some durable storage elsewhere and having a separate mechanism as a retry to pull from that durable storage to attempt to publish. You didn't add more load to your primary DB, but you lost strong consistency. Doing something "right" depends on your context. In my example, volume, the type of events and what they are used for all play a role in deciding what solution you require, not just to follow the "best practice".

    • @awmy3109
      @awmy3109 Рік тому

      @@CodeOpinion If you really needed consistency, the extra load on your dB is a good tradeoff in my opinion. Best practices come with tradeoffs but they are best practices because their benefits are more than that of other options.
      Another example is implementing SSO. Do you ditch the OpenId industry standard because it is hard to implement, requires extra network calls or roll your own that is simple but introduces security flaws?

    • @CodeOpinion
      @CodeOpinion  Рік тому

      You keep conflating rolling your own vs some pre-defined solution. I'm not suggesting that, and I have a video specifically about not doing it.