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

The road to 1.0 (or - at least - a stable API) #947

Open
ethomson opened this issue Feb 10, 2015 · 19 comments
Open

The road to 1.0 (or - at least - a stable API) #947

ethomson opened this issue Feb 10, 2015 · 19 comments

Comments

@ethomson
Copy link
Member

We've discussed a stable API for some time, and we keep pushing back on a 1.0. I'd like to restart the discussion and try to list some action items on the things that we want to consider a "solid" API that we will guarantee.

Boring rationale:
We've been using LibGit2Sharp in Visual Studio and Team Foundation Server for a few years(!) now, with great success. So much, in fact, that we have about 10 independent groups on the VS and TFS teams using it. And of course we want to ship a single copy of LibGit2Sharp in the VS or TFS installer, so that means that all of these teams have to be on the same version.

This is becoming increasingly burdensome for us, because any time there's an API change, either the person updating LibGit2Sharp has to go and find all the people using it, fix them and hope it works or simply push it on up and put the build on the floor. (Usually the former, as it turns out that breaking the Visual Studio build is not a good career decision.)


/cc @jamill 
@Therzok
Copy link
Member

Therzok commented Feb 11, 2015

We should at least postpone this until the signature removal is propagated here.

@ethomson
Copy link
Member Author

@Therzok Does libgit2 changing that interface affect our public API? (In a lot of cases our higher level API provides a buffer for such fallout.)

Great to know either way. My goal here was to get a list of action items and blockers that need to be addressed before

@Therzok
Copy link
Member

Therzok commented Feb 11, 2015

We use the same pattern (as in, passing the Signature object on each ref-related function). So that is one that should go in before we 'stabilize' API on this side.

What do we do when really breaking changes happen? Bump the whole version? We need to find ways to introduce API breakage without ending up with v34.x.y.z. (I considered we're using semantic versioning).

@ethomson
Copy link
Member Author

I admit that it will (or, I guess depending on how well thought out 1.0 is, may) become burdensome to keep the old interfaces around.

I can only think of one example of something that landed in vNext that would have made non-breaking changes actually impossible: optional method arguments. I think all the other changes that we made as breaking changes were really just fairly gratuitous ways to keep the API pretty. (Which is not a criticism, a pretty API is a noble goal. But we probably have to 1.0 someday.)

Otherwise, I can't think of any occurrences where would couldn't have kept the old interfaces alive with methods that delegated to the new improved API.

@Therzok
Copy link
Member

Therzok commented Feb 11, 2015

There will still be cases where we can't provide backwards compatibility, so stabilizing of LibGit2Sharp is at least partially dependent on a 1.0 of libgit2. My thoughts are mixed at the moment.

If API prettifying is an issue, that can be solved by keeping the Obsolete version around for a whole version bump.

But what I'm most afraid of is native signatures changing, so LibGit2Sharp might have to heavily hack some solution to keep backwards compatibility.

As long as libgit2 API isn't stable (or at least adopt an ugly-but-works apache style versioning), we can't guarantee API doesn't break.

Just my $0.02.

@ethomson
Copy link
Member Author

There will still be cases where we can't provide backwards compatibility, so stabilizing of LibGit2Sharp is at least partially dependent on a 1.0 of libgit2.

Maybe? In the example you cite (dropping signatures), it's not like we can't simply ignore the signature in the old method.

I'm not so pessimistic. But if you think that LibGit2Sharp's stable API is heavily dependent upon libgit2's stable API, then let's expand the scope of the conversation to include that.

@shiftkey
Copy link
Contributor

Super-excited to see this discussion happen!

What do we do when really breaking changes happen?

To take things beyond "what breaking changes are left to consider?", here's some thoughts on the post-1.0 world as I see it...

I think the libgit2sharp core team have been wonderfully pragmatic about breaking changes between major versions - deprecating APIs gracefully, documenting when breaking changes occur, and so on. So I'm not worried about that side of things being lost at all.

Instead I'll focus on what to do when a breaking change is necessary.

We programmers obsess about version numbers. We're sloppy with numbers initially (because we can get away with it), we then celebrate the 1.0 milestone (when we believe the API is "done"), and then shove significant changes into minor versions which causes all kinds of chaos (which we then try to justify retroactively).

I've never understood this obsession with keeping major version numbers low, and I suspect this is mostly down to this old-school mindset about "major versions are rewrites". This isn't really what SemVer is about.

So my advice would be to bump the major version with reckless abandon - because that's how it should be! If you're changing the public API surface - and your users need to actually do work as part of updating - this is how you signal your intention. So do it as often as you need to!

Of course, there are a couple of concerns with this approach:

Rapid change impacting downstream users

As @ethomson hinted at, settling on a 1.0 API won't immediately solve the problem for products which ship on a "slow" release cycle. This will impact how the maintainers work and accept changes, but I don't believe this is an impossible challenge.

@ethomson - given your knowledge of various teams at Microsoft consuming the API, do you have a ball-park figure for the cadence on how often they release? Are we talking weeks/months/quarters between releases? Teams are getting much better at shipping previews early, so I'm not really worried about this - I'd just love to get a better understanding of the downstream users.

My thinking on this is that understanding how often they release will give the team an idea of how often they should ship major releases. And having a consistent window for libgit2sharp major releases will help downstream adopt the latest versions, instead of languishing far behind because change is hard.

Changing how the maintainers work

Let's say libgit2sharp hits a point where the API is stable enough to say "okay, this is 1.0" - and it gets tagged and shipped. It might be feasible for incoming PRs to be handled in this way:

  • bugfixes - ship a new minor release
  • new APIs - ship a new minor release (Note: is this a major change?)
  • change to API behaviour - queue this for a new major release (Note: are these bugfixes?)
  • change to API contract - queue this for a new major release

So I'd expect to be managing different types of changes in-flight after each release. This requires careful targeting from the contributor, and careful reviews from the maintainer. I think vNext is still a good approach for targeting "the next major release" but then bugfixes need to go elsewhere (and then get merged into vNext).

Being clear on "what goes where" in the CONTRIBUTING docs could simplify this greatly, but I think the maintainers will have a better grasp on what this workflow would look like, and how easily it would be adopted.

@ethomson
Copy link
Member Author

So my advice would be to bump the major version with reckless abandon - because that's how it should be! If you're changing the public API surface - and your users need to actually do work as part of updating - this is how you signal your intention. So do it as often as you need to!

No, I'm not at all worried about version numbers. As you point out, they're an abstraction. It makes zero difference to me if the API breaks between 0.19 and 0.20 or between 1.0 and 2.0 if the API is breaking every few weeks. (I conflated the two, mistakenly, because we have discussed 1.0 as a non-breaking API in the past. Apologies for not clarifying that.)

Yes, semver is nice here, I suppose, because people can set their dependencies on nuget packages or something and not have to worry so much. But I'm literally talking about the speed at which we break the API.

Nor is this about our release schedule - I'm sorry that I conflated that in my discussion above about third-parties. @jamill pointed out to me offline that this was an unhelpful discussion for this issue; please consider it retracted.

I want to solely talk about how terrible it is for us to target an API that is changing constantly.

I think the libgit2sharp core team have been wonderfully pragmatic about breaking changes between major versions - deprecating APIs gracefully, documenting when breaking changes occur, and so on. So I'm not worried about that side of things being lost at all.

Right, this is where we disagree. If deprecating APIs means "removing them" and not "providing a nice compatibility layer to get to the new API" then that's not graceful.

Consider the change from taking Credentials to taking a CredentialsProvider. This single change literally took us weeks of wall clock time between grabbing a new version of LibGit2Sharp and when it landed in a build of VS. Because besides just the very obvious Git features (VS source control integration, the server, the web app) there are a dozen other little teams working on different features that Do Something with a Git repository (CodeLens, for example.)

So updating LibGit2Sharp means reaching out to all these teams, getting them to make the necessary changes and test them, investigating any new bugs... And since all these teams work gets assembled into a single box, we have to make all these changes all at once, and do so without putting Visual Studio builds or tests on the floor.

Now admittedly, most of the changes aren't quite so invasive as Credentials to CredentialsProvider. And during this particular change, we also had some distractions (holidays, security bugs to respond to) that slowed this down. Plus part of this problem is institutional. Say what you want about Microsoft, but building stable APIs is what we do above anything else. I can still build my third-party app that targeted the TFS 1.0 API from 2003 against the current TFS API.

But whatever the reason, semver isn't going to help us here. We have hit the point where we have too many people our Git library for it to be this heaving platform beneath our feet. We need a stable API to target, so either:

  1. LibGit2Sharp remains our API and API's don't break every few weeks or months.
  2. We put some shim on top of LibGit2Sharp that everybody internally targets, which allows us to absorb the breaking LibGit2Sharp changes without it rippling out to the rest of the organization.
  3. We use some other API

Obviously (1) is preferred, but it really must be one of these three as the status quo is no longer tenable for us.

@ethomson
Copy link
Member Author

I left it implicit - please let me be explicit about it: I realize that the sheer number of people using a single API in one product is my problem, and that my problems are not necessarily the library's problems.

I'm not saying that LibGit2Sharp needs to solve this problem for me - if we decide that a stable API is less important than a "perfect" API, then so be it. But I think that this is something that is problematic for more than just me, I just happen to be the person who hits it hardest thanks to scale.

@jamill
Copy link
Member

jamill commented Feb 11, 2015

Thanks @ethomson for bringing this up (and @shiftkey and @Therzok)!

I think it would be great to come to come to an agreement on what API stability means, what work needs to be done before we have a stable API, and the priority of this for the library.

As for priority, API stability is becoming increasingly important for us in particular, but I imagine also for all consumers of the LibGit2Sharp library. We have more teams here that are using LibGit2Sharp, and targeting a moving API is challenging. I suspect that this is a problem that anyone considering LibGit2Sharp has consider. LibGit2Sharp having a stable API is (in my opinion) would be valuable for the library.

I believe that we can continue adding new features and moving the project forward while maintaining a backwards compatible API for a predictable amount of time. I would like to see what we need to do to get to this point, and come to an agreement that this is important (it is for at least a subset of us using the library). While related but a slightly different issue - I think declaring a 1.0 would be a great way to signify this commitment to API stability and the project's confidence in itself.

@nulltoken
Copy link
Member

I think we've reached a point where no major blocking features are missing. So stabilization would actually make sense.

I'm also worried a bit by potential changes at the libgit2 level but I have the feeling that's something we should be able to cope with.

Now, although much work has been done in order to get a (as) uniform (as possible) interface, some things are still clunky, or missing, or hard to leverage.

We now rely on defaultable parameters. However, those being dealt with at compile time, they do not play well in a non breaking change world. This means a ton of potential overloads to create (and maintain). Maybe making some optional parameters less optional should be considered as well.

To this purpose, I've created a dedicated label. Let's harvest together the API and create an issue per topic, labeling it stabilization.

This should give us a first view of the amount a work to tackle. Then we'll decide which one are worth working on.

Thoughts?

/cc @dahlbyk

@haacked
Copy link
Contributor

haacked commented Feb 11, 2015

This means a ton of potential overloads to create (and maintain). Maybe making some optional parameters less optional should be considered as well.

Given this is a library, I'd recommend having as few overloads as possible on the direct interfaces and using extension methods to provide most of the "overloads". That keeps the interfaces small and makes them easy for others to implement and they get the overloads for free. Of course this really depends on the specific cases. For example, if a class is concrete without an interface, by all mean overload the hell out of it, but maybe just only make the method that takes in the full complement of arguments virtual. 😄

@carlosmn
Copy link
Member

My main worry with declaring the libgit2sharp API stable is that libgit2's isn't. While a bunch libgit2's changes affect the way we do stuff because of C and won't affect the C# API, I worry whether a 1.0 (or whatever) version would get stuck on a particular version of libgit2 because we've realised that we need to perform some operation in an incompatible way in order to remain compatible with git (like the reflog-message PR currently, it turns out the data is used differently as we expected).

I don't expect there to be many situations like this left in the libgit2 API before we declare a stable one, but it's something for which there should be answer.

@haacked
Copy link
Contributor

haacked commented Feb 11, 2015

So my advice would be to bump the major version with reckless abandon - because that's how it should be! If you're changing the public API surface - and your users need to actually do work as part of updating - this is how you signal your intention. So do it as often as you need to!

I mostly agree with this. Keep in mind, every change is potentially a breaking change. Someone could be using private reflection or using the fact that method call takes a certain amount of time as a means of preventing a race condition. As Raymond Chen wisely teaches us, developers take dependencies on stupid shit.

So my point is, I personally wouldn't necessarily increment the major version on every breaking change. I'd come up with some general idea of how much a change affects the consumers and make a decision based on that. If you change a single public method that is likely to only affect .05% of consumers, perhaps only increment a minor.

@dahlbyk
Copy link
Member

dahlbyk commented Feb 12, 2015

Right, this is where we disagree. If deprecating APIs means "removing them" and not "providing a nice compatibility layer to get to the new API" then that's not graceful.

To clarify, is the issue here just that we have been aggressively pruning Obsolete members? I'd like to think we've been paying pretty close attention to avoiding flat out breaking changes without a (brief, at times) warning period. If we've lived up to that goal, then the fix seems relatively simple: deprecate and document aggressively, and Obsolete or remove on a much longer timeline.

@nulltoken
Copy link
Member

I've gone through the opened issues and some of them have now been labelled stabilization.

Some may make sense, some are more subject to debate (either should be closed or postponed).

Please add comments to them to express your thoughts.

@ethomson
Copy link
Member Author

@dahlbyk Yeah, I think that's relatively accurate. I don't think that there's necessarily a lot of work here or that it's a particularly hard problem. :)

@rubberduck203
Copy link
Contributor

I wanted to chime in here as someone who uses your project. I've read through the conversation and I can understand the concerns about lig2git's API not being stable yet. I also understand the concern about your API not being stable. Personally, I've isolated my code from your library as best I can because I understand that the API isn't solid yet. However, I'm only using a very small subset of the functionality this project provides. Running the library through a set of interfaces won't be practical for all projects. At some point the API needs to stabilize. Only the active contributors can decide when that will be, but after five years of development, something eventually has to give. Sooner or later, you'll need to commit to an API that will (within reason) not change. Or, at least not change drastically very often.

My two cents as a user, for whatever it's worth.

@jwdonahue
Copy link

Wow, I think the first and last time I included LibGit2Sharp in an application, was more than ten years ago. I am shocked and dismayed that this project is still unstable, as indicated by the zero dot version number. If it were possible, I would personally apologize to the maintainers of the code I left behind.

Is it really so unstable, that it doesn't deserve a 1 dot version number?

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

10 participants