ShaneGH / magic-odata

Magical OData client generation for typescript
MIT License
1 stars 0 forks source link

Alternative #81

Open texttechne opened 1 year ago

texttechne commented 1 year ago

Hi, I'm really unsure if I should open this ticket, but here I go. I'm the author of odata2ts. And it actually does what magic-odata does. Have you considered this as alternative?

Don't get me wrong: It's always cool to have alternatives and there also exist quite different reasons to have them. But since I know how long the road is to generate a full featured OData client (nearly impossible :wink:)...

Anyways, I hope I don't step on your toes by writing this.

But then again, I'm curious and if you have considered it as alternative and wanted to have a different tool, then I would be highly interested in those reasons.

ShaneGH commented 1 year ago

Hey @texttechne thanks for the input. I had a quick look through odata2ts, not enough to fully understand it, but I can see some key differences in the focus between projects.

I am sure to miss a lot of things, so please fill in any features which are available in odata2ts and not in magic-odata, or features which are available in both

odata2ts

magic-odata

As you said, odata is a massive set of features, and it is not only clients that implement a subset of functionality, but also servers. My guess is that the flavor of the client will depend on which OData server was the primary influence for the project, and that odata2ts and magic-odata have been influenced by different server implementations

ShaneGH commented 1 year ago

@texttechne if you would like, there is an odata $metadata file buried in magic-odata that handles a lot of edge cases. Feel free to grab it if you would find it useful for TDD https://github.com/ShaneGH/magic-odata/blob/main/tests/magic-odata-tests/code-gen/namespaces/namespaces.xml

texttechne commented 1 year ago

Hey @ShaneGH, you're definitely right: different server technologies is the main factor which drives the different implementations. I live in the SAP and you in the Microsoft world... actually a brilliant point to join forces 😀

Ok, here is my try of a comparison.

Feature comparison

magic-odata

Currently, I'm working on $value, primitive properties and maybe $count.

My main problem with some of the advanced querying features ($root, $this, ...) is that I'm lacking a test server which supports these features. The SAP stuff is quite limited in that respect and the official implementations I know (TripPin, OData, Northwind) also don't support such stuff. And without any way to at least test this functionality manually I won't implement it.

And some of those features like parameter aliases are actually not needed => see querying philosophy

odata2ts

For completeness' sake, some stuff you've mentioned missing has been implemented:

Querying Philosophy

There's one main difference: both implementations approach querying differently.

odata2ts is heavily inspired by Java tools like QueryDsl or jOOQ which generate "query-objects" to provide filter functionality. So the odata2ts query is completely type-safe and abstracts away all OData intricacies (which have become less with V4):

odata2tsClient.navToPeople().query((builder, qPerson) => 
  builder
    .filter(
      // lastName will only offer string based operations and requires string as argument
      qPerson.lastName.contains("x"),
      // age as number property only accepts numbers
      qPerson.age.gt(18)
    )
    .expanding("trips", (tripsBuilder, qTrip) =>
      tripsBuilder
        .select("budget")
        .orderBy(qTrip.budget.desc())
        .top(1)
    )
)

magic-odata follows a more generic approach as far as I can judge:

odataClient.People
  // the person object is generated right? 
  .withQuery((person, { $filter: { containsString, eq } }, params) => 
    containsString(person.LastName, "x")
      // is the value comparison for numeric prop a string or a number? i guess string because it's generic?!
      .and(eq(person.Age, "18"))
  )
  // sorry, here I'm lost: how do I specify the expanding now?

So, magic-odata relies on the knowledge of the user when building queries: containsString can be combined with any property, e.g. person.Age, so that the user needs to know the type of the property in order to choose the right operators.

Extensibility

odata2ts keeps two aspects open for extension:

Two HTTP client implementations are provided (Axios, JQuery) but it should be easy enough to implement any other technology according to the API. This approach was chosen since some advanced features should be handled by the HTTP client and not backed in into odata2ts:

The other main concern is data types. When confronted with something like Edm.DateTimeOffset it should be up to the user to choose which type it should be converted to: keep it as string, JS Date, Moment / Luxon, ... This is what converter-api and the converter packages achieve.

ShaneGH commented 1 year ago

@texttechne emailed you

ShaneGH commented 1 year ago

@texttechne all of this makes sense, cheers. A lot of the stuff you have mentioned is available, just in a slightly different way to odata2s. The more we talk the more I see the equivalence of these two packages

Regarding your issue with test servers and $root and $this, my philosophy on it has been:

  1. If servers don't implement it, then users won't be able to use it anyway (what will they query with it?)
  2. This allows experimentation in both the interface (the generated type code) and the implementation (the odata query string). The stakes are low and it is ok to make mistakes if no-one will actually use it (If a tree falls in the forest...)
  3. These were some of the most fun features to implement as they really challenge the underlying architecture, so they are worth doing

Regarding extensibility, magic-odata takes a much more low level approach. Rather than come up with a list of things that could be extended, magic-odata goes with a middleware like approach with interceptors available at all levels of http request generation

This allows things like headers, caching, auth and even http client types to be completely customizable either globally or per request

Specifically regarding $value/$count

Other parts of the spec like, http status codes, also introduce concepts which are not possible to model in typescript (they are not known at compile time), and so are also considered out of scope (for now, although with a long enough time frame, this will be addressed)

Regarding the code snippet of magic-data (odataClient.People.withQuery(....)

  1. // the person object is generated right?
    • Yes :)
      1. // is the value comparison for numeric prop a string or a number? i guess string because it's generic?!
    • This code won't actually compile. The second arg of eq must be the same type as the first. Assuming that age is a number, the code should look like: .and(eq(person.Age, 18))
      1. // sorry, here I'm lost: how do I specify the expanding now?
    • The output of the lambda in withQuery can be a single item (e.g. the filter specified in the code snippet) or an array of items, where you can return multiple query elements

Regarding data types, care has been taken to fully support all data types from a query point of view (caveat1: not including geography and geometry, caveat2: odata2ts#106 is also an issue in magic-odata). From a response data type point of view, magic-odata sticks to json representations of Edm types by design, and considers something like luxon out of scope. Again, parsing luxon dates can be achieved with low level extensibility (above) if required. Personally, I would love to get a feature request to do something different here, but I can't see it happening

Regarding some misc points not covered above.

texttechne commented 1 year ago

Hi @ShaneGH, fruitful discussion!

Regarding your issue with test servers and $root and $this, my philosophy on it has been: allow for experimentation

You're right there and you do make some really excellent points. I'll switch my philosophy there => convinced! 😁

And it should be clear by now, that both libs implement nearly the same feature set. I'll try to do a high-level comparison later on.

Regarding the data types: this has been a major concern for odata2ts, hence the converter-api package. When handling the response you will present your users with the correct typing. But if I want to use a different data type in my code - which is more like a necessity when it comes down to V2 data types, e.g. Edm.DateTime (\Date(....)\) - then the generated models get out-of-sync, so to say. Doing this part in the interceptor is, of course, achievable, but your users don't have the correct types and need to create them on their own.

Taking this one step further and this is my holy grail for the project (#107), when $selecting or $expanding the reponse structure is shaped. Since this information is readily available in the query builder it should get reflected in the typing of the response...