Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative #81

Open
texttechne opened this issue Apr 19, 2023 · 6 comments
Open

Alternative #81

texttechne opened this issue Apr 19, 2023 · 6 comments

Comments

@texttechne
Copy link

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 😉)...

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
Copy link
Owner

ShaneGH commented Apr 20, 2023

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

  • OData V2 support
  • More focus and support for primitive data types (converters, GH issues related to large numbers)
  • JQuery/Axios
  • Better package.json support

magic-odata

  • Namespaces
  • Typed canonical functions
  • Automatic handling of strings/dates/guids
  • Casting
  • Advanced querying and byref ($root, @params)
  • Angular (rxjs)
  • Advanced $expand + $levels
  • Svalue
  • Scount
  • Indexing ordered collections
  • Issue tracking for full odata v4 support

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
Copy link
Owner

ShaneGH commented Apr 20, 2023

@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
Copy link
Author

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

  • more query options: basically what you've named
    • raw values via $value (but manual config of headers with content-type: text/plain is probably required?)
    • primitive attribute values probably (e.g. /People/Description)
    • $count as the endpoint
    • Indexing ordered collections
    • $root
    • @params
    • casting
    • $levels
  • Angular support

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

  • documentation page: odata2ts.github.io
  • dedicated CLI: here you could probably take over some things of odata2ts
  • TS based configuration file
  • supports also only model / type generation: see the use case
  • more query, especially filter options (😉), see querying:
    • any / all
    • simple groupBy
  • workarounds for faulty implementations
  • renaming
  • type converters
    • configurable
    • extensible
  • open for any HTTP client
  • support for singletons: but I don't know if that's really missing in magic-odata
  • $search supports phrases as well as terms
  • request configuration as in configuring headers per request

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

  • advanced $expands
  • typed canonical functions: if you mean by that support for bound/unbound operations
  • Automatic handling of strings/dates/guids => that's what the converters are for

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:

  • HTTP Client
  • type converters

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:

  • auth
  • CSRF protection
  • E-tag support
  • batch requests
  • ...

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
Copy link
Owner

ShaneGH commented Apr 20, 2023

@texttechne emailed you

@ShaneGH
Copy link
Owner

ShaneGH commented Apr 22, 2023

@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

  • magic-odata will send the appropriate accept header for these queries
  • magic-odata will read content-type headers and can process json. Without content-type a response is considered a string. This case is not possible to model with typescript in a strict manner

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 :)
  2. // 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))
  3. // 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.

  • "parameter aliases are actually not needed"
  • "dedicated CLI"
    • For sure, the odata2ts CLI is clean and simple
  • "any / all / groupBy"
    • magic-odata has any/all as part of $filters
    • I am hesitant to look at aggregations because of the sheer scale of it combined with general lack of support from server implementations. Personally, I think this is one of the areas where the odata spec drops the ball. It is just too complicated to achieve any wide spread adoption
  • "support for singletons"
    • singletons are supported and treated exactly the same as entity sets
  • "$search supports phrases as well as terms"
    • It is actually the opposite, but the language is wrong. Only phrases are supported. I didn't see this as an issue as the magic-odata test server treats one word phrases as terms. Have you seen any different?

@texttechne
Copy link
Author

texttechne commented Apr 22, 2023

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants