Giter VIP home page Giter VIP logo

Comments (30)

fitzgen avatar fitzgen commented on July 30, 2024 3

Now hopefully we won't run into the issue of breaking changes very often at all, since our API design process attempts to front-load a lot of questions that if left unanswered would become breaking changes down the line. But it is inevitable that we will eventually run into some scenario where we must decide whether to do a breaking change or not.


My personal opinion is that we should make an effort to minimize breaking changes, but that we shouldn't bend over backwards to do so. We should definitely coalesce many small breaking bumps into one release when possible. However, due to the way that breaking bumps cascade up to the umbrella crate, it seems like it will be quite hard to completely avoid breaking changes at that level. We should accept that as a reality.

Regarding versioning the umbrella crate and the individual crates, I think we should attempt to version them independently. This would alleviate some of the pain of breaking version bumps, since individual crates will have a less tumultuous version history than the umbrella crate which must do a breaking change if any of the other crates do. A little extra maintenance outweighs a bunch of user-facing pain here.

from gloo.

Pauan avatar Pauan commented on July 30, 2024 1

Backwards Compatibility

My opinion is: avoid breaking changes when possible.

And when breaking changes become necessary, try to put multiple breaking changes into a single release.

And only do breaking changes once in a while (let's say, one breaking release every 6 months, not sooner).

This allows us to (slowly) fix bad APIs by doing breaking changes, but minimizes the amount of churn for consumers (since they only need to upgrade once every 6 months).

And obviously it should follow the proper deprecation cycle, to give time for users to upgrade.

Do we want to keep each individual gloo_whatever crate's version locked to the same version as the umbrella gloo crate or not?

I say no. Each should be versioned independently.

However, the maintenance burden is larger for Gloo maintainers.

Is it a big problem? Can we fix it? For example, we can do lockstep releases where we publish all changed packages at the same time (even though each package might be at a different version).

from gloo.

Aloso avatar Aloso commented on July 30, 2024 1

@Pauan What benefit does that have?

It means that, by looking at the version numbers, you immediately see which crates were updated in the last release (because their versions match the version of gloo). But maybe it was a bad idea.

from gloo.

thedodd avatar thedodd commented on July 30, 2024 1

breaking changes

Definitely agree with the sentiment you are presenting overall @fitzgen. We should minimize breaking changes as much as possible. The front-loaded design process will definitely help with that. That being said, we shouldn't completely eschew breaking changes. Grouping them together is a good call as well.

child / parent version sync

Keeping the child crates' versions pinned to the parent create seems like a pretty serious maintenance overhead, and I doesn't seem like we would gain much from doing this. So, I agree with @fitzgen's initial statements on versioning child crates independently.

deprecation / breaking release cycle

Full agreement with @Pauan's analysis above.

versioning parent crate

Technically, if the parent gloo crate just re-exports the other child crates, then its semver API depends on, and only on, the API of the child crates. As such, in terms of API versioning, we will need to update the parent crate appropriately as child crates are updated.

Consider the following example.

gloo @ 0.1.0
  - gloo_timers @ 0.1.0
  - gloo_events @ 0.1.0

If some updates are made to gloo_events in a backwards compatible fashion (a new type, a new method &c), then we will obviously update gloo_events to 0.1.1.

What do we do to the parent gloo then? It simply re-exports the API of gloo_events, so it must update its own version according to the nature of the update in gloo_events. So we end up with:

gloo @ 0.1.1
  - gloo_timers @ 0.1.0
  - gloo_events @ 0.1.1

Next, a similar update hits gloo_timers. Backwards compatible, say a new method is added. We update gloo_timers to 0.1.1. Similarly gloo must be updated to reflect the nature of the API change as well. So now we end up with:

gloo @ 0.1.2
  - gloo_timers @ 0.1.1
  - gloo_events @ 0.1.1

from gloo.

alexcrichton avatar alexcrichton commented on July 30, 2024 1

@thedodd I think I agree yeah about being able to talk about gloo, although I just wanted to clarify that "I'm using gloo v0.1.0" is not equivalent to "I'm using the same dependencies as at the time gloo v0.1.0 was published". I think that's ok, though, where if someone says what version of gloo they're using it still means something in terms of minimum versions in use if we bump gloo with subcrate bumps as well

from gloo.

Pauan avatar Pauan commented on July 30, 2024 1

@alexcrichton Rather my thinking of choosing this cascading bump is to have something to talk about.

That's a really good point that I hadn't thought of. If nothing else, it serves as marketing, showing other people that the crate is alive and receiving steady updates.

Given the above (and the obvious benefits of regenerating the docs), I'm now also in favor of option 2.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024 1

So it seems to me like we have consensus on option (2). I'll write up a couple sentence summary of this approach for the CONTRIBUTING.md and then I think we can close this issue. (And if/when we start feeling maintenance growing pains, we can remind @alexcrichton of his offer to script publishing ;) )

Does anyone have concerns with moving forward in this way?

from gloo.

Nemo157 avatar Nemo157 commented on July 30, 2024 1

There's a lot of great information in this thread and I've seen it linked to elsewhere for discussions of managing workspaces so I'd like to just clarify a couple of small things:

Whenever any crate changes, bump the version of all the crates. This ensures the version of every crate is the same (this is what futures-rs does).

This is almost certainly just an artifact of the extended alpha that futures v0.3 is having. Once there is a stable release I would expect futures to switch to a scheme close to option 2 (but there hasn't been any discussion on exactly how it will be versioned yet).


[exact version constraints...] would have the downside of likely introducing duplicate versions of Gloo crates in scenarios like [see source post]

Cargo doesn't allow multiple semver compatible versions of a crate so this would result in a resolution failure (this might change with public/private dependencies).


Something I didn't see mentioned is supporting --minimal-versions. If you don't bump the version number of the main crate when sub-crates are updated that makes it impossible for a downstream crate that is guaranting its minimum versions to ever use new features when depending on the main crate, it will have to depend directly on the sub-crates so that it can bump the minimum versions as needed.

from gloo.

Aloso avatar Aloso commented on July 30, 2024

I think we shouldn't change the version of crates that weren't modified:

gloo @ 0.1.3
  - gloo_timers @ 0.1.3
  - gloo_events @ 0.1.2

However, if the version of gloo_events is incremented after that, it should skip 0.1.3.

The problem that I see is this: When gloo grows and new APIs are included, these APIs might cause a lot of breaking changes, as is common with very young crates.

One way to remedy this, is to release new APIs as standalone crates, and include them in gloo only when they are stable enough. This might be appropriate after gloo reaches 1.0.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

@Aloso However, if the version of gloo_events is incremented after that, it should skip 0.1.3.

Why would it skip 0.1.3?

from gloo.

Aloso avatar Aloso commented on July 30, 2024

@Pauan Why would it skip 0.1.3?

So it has the same version as the umbrella crate. It's just an idea.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

So it has the same version as the umbrella crate. It's just an idea.

Okay... but why? What benefit does that have? Why only sync the versions sometimes and not other times?

from gloo.

Pauan avatar Pauan commented on July 30, 2024

It means that, by looking at the version numbers, you immediately see which crates were updated in the last release (because their versions match the version of gloo).

Ah, okay, so you're saying that the gloo crate should always be version bumped anytime a sub-crate is version bumped.

That's a valid idea, but personally, I don't think it's necessary: the gloo crate shouldn't need a version bump at all (unless its metadata changes, or a breaking change happens). Semver takes care of non-breaking changes.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

What do we do to the parent gloo then? It simply re-exports the API of gloo_events, so it must update its own version according to the nature of the update in gloo_events.

I don't think it is necessary, according to the rules of semver.

In fact, the gloo-events dependency in gloo doesn't need to change at all, and gloo itself doesn't need to change at all, because semver will automatically pull in the latest version.

So it is only when a breaking change occurs that we need to even touch the gloo crate at all.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

I don't think it is necessary, according to the rules of semver.

That would only be true if we are not attempting to present the parent crate as an API in and of itself.

  • If that is indeed the case, then there is no functional purpose of the parent crate other than providing a more simple way to include all of the child crates.
  • If a user is depending on the parent crate, not as an API, but as a way to include one of the child crates, then the parent is pretty much just an unnecessary abstraction.

I agree that we could choose to only version the parent on breaking changes from a child, but what we loose is the ability to refer to the overall state of the Gloo API. Take the tokio project as an example. They've been doing the same thing I have suggested above (successfully, it would seem). For a supporting example of this versioning behavior, check out this recent tokio release.

You can see from the tokio changelog (which is analogous to our parent Gloo crate) that it was bumped from 0.1.17 -> 0.1.18 due to re-exported changes in some of the child crates, specifically the introduction of a new type in tokio-executor (the new TypedExecutor), and the implementation of this trait by some other types in peer child crates. Note that these were not backwards incompatible changes.

  • each child crate had its version incremented according to semver standards.
  • the parent crate (tokio) received a similar version update because it is the parent crate which exposes a uniform view into the child crates by way of re-exporting their APIs.

To summarize: if we are not actually going to use the parent crate as a representation of the Gloo API overall, then there really is not much need for it in the first place. If it is representative of our Gloo API overall, then according to semver, we should version it accordingly.

Seems logical to follow tokio's example on the versioning front, IMHO.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

If that is indeed the case, then there is no functional purpose of the parent crate other than providing a more simple way to include all of the child crates.

Yes, the primary purpose is for convenience.

This is emphasized even further by the fact that gloo simply re-exports the sub-crates (so you'd use gloo::events::foo rather than gloo_events::foo)

As a general rule, libraries should depend upon individual crates, and applications should use the umbrella crate (purely for convenience).

the parent is pretty much just an unnecessary abstraction

I do not think so. Convenience is quite important. If it weren't, then there would be no need for futures or tokio (or other umbrella crates).

For a supporting example of this versioning behavior, check out this recent tokio release.

Taking a look at their release system, it seems the primary reason they bump the main version is because they also bump the docs.rs link at the same time. I believe that is unnecessary, but it is their choice.

In most cases they don't even bump the dependencies, only the docs.rs link and the version. So they aren't quite following what you are suggesting.


There are multiple ways to handle these "umbrella" crates (futures-rs uses a system where every version is synced), so I don't think there is a clear precedence one way or the other.

So that means we have essentially three different systems:

  1. Whenever any crate changes, bump the version of all the crates. This ensures the version of every crate is the same (this is what futures-rs does).

  2. Change each crate individually, and in addition bump the version of the main crate on every change (this is what tokio does, more or less).

  3. Change each crate individually, and only change the main crate when needed (e.g. on breaking changes).

I don't have strong opinions about any of these systems, they each have their pros and cons.

I think option 1 is the worst, since it's high maintenance, and it forces version bumps even when not needed. However, it does make it clear that the sub-crates are related to the main crate.

I think option 2 is reasonable, but it has some maintenance burden, and it means that the main crate will have a version which is much higher than the version of any of its sub-crates.

I think option 3 is the least maintenance, though users might incorrectly believe that the Gloo API isn't changing (even though it is).

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

@Pauan

In fact, the gloo-events dependency in gloo doesn't need to change at all, and gloo itself doesn't need to change at all, because semver will automatically pull in the latest version.

Initially my gut instinct was very much against letting cargo/semver do nested updates without bumping the umbrella crate version, but I find myself unable to construct a strong argument from first principles, so I guess it seems OK to me.

The one thing I will note is that we haven't ruled out the gloo crate having some, well, glue to integrate the toolkit's crates together if it makes sense and doesn't belong in any of the individual crates. It is possible that this glue might require new functionality introduced in a semver compatible release of a nested crate, which would force the bump. (Of course we could always delay the umbrella crate bumps until if/when we start running into this)


So that means we have essentially three different systems:

Yeah, these are the main options I see.

I think option (1) is easiest from a maintenance perspective, since it is very easy to script publishing and updates (as we've done with wasm-bindgen). It essentially represents the complete removal of edge cases that options (2) and (3) introduce. But I don't think we should follow this approach with Gloo as I've expressed up thread.

I was in favor of option (2) previously, but as stated above the horizontal rule, I guess I am now in favor of (3). (Although I'd note we would also need to bump the minor version when adding new gloo_whatever crates, and breaking changes wouldn't be the only version bumps required).

from gloo.

Pauan avatar Pauan commented on July 30, 2024

It is possible that this glue might require new functionality introduced in a semver compatible release of a nested crate, which would force the bump.

Although I'd note we would also need to bump the minor version when adding new gloo_whatever crates,

Right, I didn't mean to imply that we'd literally never do minor bumps, just that we wouldn't bump it solely for child crates updating.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

One thing to consider: I haven't tested it, but I believe crates.io only regenerates docs when the version is bumped, so that's an argument in favor of 2, since we want new APIs to show up in the gloo crate docs.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

@fitzgen && @Pauan I definitely think that option 3 is easiest for us as maintainers, but not for users. I would like to critically emphasize that option 3 is not semver compliant. Let us remember that semver applies to whatever is declared to be the "public API" of some software system. Just consider the following scenario:

A user depends on the parent gloo crate, and they are using a few of the subcrates. Let's say they are depending on gloo @ 0.2.0.

  • The gloo team decides to publish a few crate updates to some of the child crates. These are backwards compatible changes. However, due to how option 3 is being proposed, the parent crate does not move, and it stays at gloo @ 0.2.0.
  • the user does a cargo update, and because their dependency is on the parent crate, the umbrella crates will be updated behind the scenes.
  • now, any bugs which may have been introduced in the child crates may now impact that user, and they no longer have a way to refer to an actual gloo API @ version 0.2.0. The parent's version essentially loses its meaning, because the API of the gloo crate is now transient. Under option 3, there would literally be no real 0.2.0 version of Gloo.
  • not only does this violate semver, but it also removes much of the convenience of having a parent crate in the first place. Users can no longer attempt to pin to exactly =0.2.0 in their Cargo.toml.

I hope I am clearly expressing the semver violation. I hope I am not communicating abrasively or anything like that, but I guarantee that this will cause problems for users. We have probably all experienced these sorts of issues before. I know I certainly have.

The typical counter argument to this is: "well, just have the users pin the exact version of the child crates which they depend on."

  • this defeats the purpose of having a parent crate. It is no longer a convenience, it is a hurdle.
  • trusting the API of the parent crate is no longer a thing.
  • it is still a violation of semver (unless we literally tell folks that the parent crate is not a public API, it is just a way to pull in some glob of child crates which might have their versions change at any time).

In the example of tokio which I mentioned above (which is accurate), the parent tokio crate is updated to reflect the API change of its child crates, because the parent crate represents the API of the tokio ecosystem (child crates) as a whole (updating documentation links is certainly not a majority of what is done, is is just a thing which is part of any release). This allows users of tokio to logically refer to exact API states. It is not transient, which option 3 would lead to.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

I would like to offer an additional point of clarification here, in case there is confusion. If the parent Gloo crate had its own code, and was not re-exporting child crates as part of its API, then option 3 would not only be the best way to go, it would be the only pattern which would be semver compliant.

However, because we are publicly re-exporting the code of child crates in the parent Gloo crate, according to the Rust language semantics, these are now part of the crate's public API. As such, changes to that re-exported code now constitute as API changes, by definition.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024
* any bugs which may have been introduced in the child crates may now impact that user

This is true of bugs introduced in all semver-compatible releases.

However, because we are publicly re-exporting the code of child crates in the parent Gloo crate, according to the Rust language semantics, these are now part of the crate's public API. As such, changes to that re-exported code now constitute as API changes, by definition.

I don't think anyone is arguing that isn't the case.

You could consider that a bug fixed in gloo_whatever isn't fixed in the umbrella gloo crate until gloo updates its dependency on gloo_whatever to the bug-fixed version. This is a good argument for bumping gloo patch version and updating its dependency on gloo_whatever.

But a new bug introduced in a new, semver-compatible gloo_whatever release still can't be avoided via depending on the previous gloo release either way, since cargo will select the newest release that has this new bug.

The only way to avoid new bugs is to pin exact version dependencies: =X.Y.Z. Are you arguing for making the umbrella crate use these dependencies? In addition to avoiding hypothetical new bugs, it also avoids hypothetical bug fixes and perf improvements. Furthermore, this would have the downside of likely introducing duplicate versions of Gloo crates in scenarios like:

- my_project
  - gloo @ 0.2.0
    - gloo_whatever @ =0.1.4
  - cool_helper_crate @ 1.2.3
    - gloo_whatever @ ^0.1.5 (or alternatively =0.1.5)

Now we have two copies of gloo_whatever in your wasm, doubling code size impact.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

@fitzgen

This is true of bugs introduced in all semver-compatible releases.

Yes, but the differences is that in a semver-compatible release, you have a new version to point to, like gloo 0.2.1. With option 3, we would not have a version 0.2.1, it would all still be 0.2.0. Which is exactly why I am emphasizing that option 3 is not semver-compliant.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

As far as the last question, no. I do not think we will need to do that. We can still express the subcrate dependencies in standard "compatibility" format (eg, 0.1.5 instead of =0.1.5). This should be fine, as long as we are updating the public API version of the parent crate and its Cargo.toml.

The Cargo.toml will need to be updated in the parent as well, because if we release a parent version of 0.1.26 which depends on a child crate at 0.1.5 (where the new feature has been implemented), it will be important for us to ensure that "at least" version 0.1.5 is pulled in.

from gloo.

thedodd avatar thedodd commented on July 30, 2024

@fitzgen what you are saying is true that if we release versions of child crates which are backwards compatible, they will be pulled in as part of a cargo update if they are not strictly pinned.

I think that it would be best not to pin them, because if it is just a single child crate which has the bug in it, then a user can easily patch that in their Cargo.toml until we get a new release out, without the user having to potentially revert other features which they need.

Avoiding duplicates is good, for sure. That is usually a pain as well. I think that following the pattern which tokio uses is probably a good bet for us as well. Update parent crate to reflect real API changes, but do not strictly pin child deps, as this allows users to patch versions as needed.

from gloo.

Pauan avatar Pauan commented on July 30, 2024

@thedodd now, any bugs which may have been introduced in the child crates may now impact that user, and they no longer have a way to refer to an actual gloo API @ version 0.2.0.

That's true with all the options though, because umbrella crates do not pin at specific versions, so cargo will always pull in the latest version of sub-crates. So downgrading to a previous version of gloo will not help.

The only way to solve that problem is to downgrade to a previous Cargo.lock (which will work with all the options, including 3).

The parent's version essentially loses its meaning, because the API of the gloo crate is now transient. Under option 3, there would literally be no real 0.2.0 version of Gloo.

This is true with all the options though.

Users can no longer attempt to pin to exactly =0.2.0 in their Cargo.toml.

Pinning does not help with any of the options. Only Cargo.lock solves that problem.

I hope I am not communicating abrasively or anything like that, but I guarantee that this will cause problems for users.

I don't think you're being abrasive, and I don't see how presenting neutral arguments could be seen as abrasive.

And I don't want to encourage a culture that views neutral arguments as being abrasive, since that's very harmful toward progress.

We have probably all experienced these sorts of issues before. I know I certainly have.

Absolutely, which is exactly what Cargo.lock was intended to solve. None of the options solve this problem, but Cargo.lock does solve it.

This allows users of tokio to logically refer to exact API states.

Except it doesn't, because sub-crates will still be updated regardless. And downgrading to a previous version of tokio does not downgrade the versions of the sub-crates.

from gloo.

alexcrichton avatar alexcrichton commented on July 30, 2024

I thought I might jump in as well and give my opinion on this. I've tried to read this thread and get up to speed, but sorry if I duplicate something!

I agree that the options @Pauan outlines above are a good representation of the state of play. My personal opinion is that option (2) is the best. In my own words I would expect that whenever a crate is bumped all crates which depend on it have their version requirement on that crate bumped. Those crates themselves get a bump, and it continues up to the root crate bumping crates. (sort of a cascading bump, but crates which don't depend on the original crate at all aren't bumped).

My rationale for this is slightly different from what's already mentioned though. FWIW @thedodd I believe @Pauan's most recent comment is correct. Unless you're using pinned dependency versions (=x.y.z) there is no way to refer to a crate release at a point in time. If gloo is bumped a lot and has tons of versions, creating a new project with 0.1.0 as a version requirement vs 0.1.124 will generate the same lock file due to Cargo eagerly pulling in more recent dependencies. (and as a side note, although wasm-bindgen uses pinned dependency versions it's a different use case that I would not recommend for gloo).

Rather my thinking of choosing this cascading bump is to have something to talk about. I think the gloo crate is probably going to be pretty popular, especially with tutorials and such. Being able to talk about the crate as a concept would be quite nice, and you can easily compare with someone "are you on gloo x.y.z?". If you're on 0.1.0 and I'm on 0.1.4 it doesn't mean we're for sure using different dependencies, but you know that I'm using at least 0.1.4 and all the latest crates at that time. While the docs.rs updates and such are nice perks to the strategy, I think having a tangible thing to talk about is pretty important as well (our own documentation, etc, etc).

I do agree that this strategy can be more burdensome on maintainers, but this is I think imminently solvable with a publication script like wasm-bindgen, and I'd be fine volunteering to write it!

from gloo.

thedodd avatar thedodd commented on July 30, 2024

there is no way to refer to a crate release at a point in time

@alexcrichton yea, when dealing with a parent crate which re-exports child crates without exact version pinning, that is definitely the case.

I suppose the main thing I am attempting to communicate is the importance of public API representation in the parent Gloo crate. Though cargo uses "latest compatible" semantics by default, I do still think there is quite a lot of value in being able to refer to public API versions, even if it is only in terms of documentation and features.

@Pauan, what you are saying about the lock file is certainly correct as well.

from gloo.

fitzgen avatar fitzgen commented on July 30, 2024

Pull request over in #74

from gloo.

jhpratt avatar jhpratt commented on July 30, 2024

Just coming across this issue — I agree that option (2) seems the best, and have no objections. The PR also looks good to me.

from gloo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.