9

The Expand and Contract Pattern in JavaScript | by Fernando Doglio | May, 2021 |...

 3 years ago
source link: https://blog.bitsrc.io/versioning-your-components-through-the-expand-contract-pattern-bc31afaae623
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

A practical example: updating my component

Let’s take a look at it from a practical PoV: I’m going to update a composite component I created recently using Bit, for a different article talking about independent components and the composite pattern.

My composite component

The current interface for my component right now is this one:

But the problem is that I’m now realizing that the traverse method makes no sense to have it go through the entire thing. I want it to apply the function fn to a particular node, using the name as ID.

The problem? This is definitely a breaking change. If I were to update that code and release the new version, every developer out there thinking they should be using the latest version “because it’s probably better” would have their code stop working.

Even worse, I would’ve stopped supporting their use case out of the sudden without prior notice. This is terrible DX!

Instead, I can Expand & Contract my code. Let me explain what that means:

There are 3 stages to this pattern: Expand, Adapt and Contract.

1*EW4wgCJzR98Y_i8Uo3pbEw.png?q=20
versioning-your-components-through-the-expand-contract-pattern-bc31afaae623

The Expand phase

During the first one, the “Expand” phase, I’ll have to add the code I want as my final version but I want it to coexist with the current implementation. Essentially I want to grow (thus the “expand” name) my code base into supporting both versions (the new and the old).

This is not a particularly easy feat to accomplish, depending on the type of project and the technology you’re working with, this can be as simple as providing an overloaded version of a method or as complex as having a single endpoint of an API do two different things.

The point of this phase is that it is NOT a permanent fix. The expanded state is not going to last, but it’ll give your users enough time to go into the Adapt phase without breaking their entire code base without a single warning.

In my case, my component’s traverse method will now change its signature to look like this:

Notice how I added a second, optional, attribute. This attribute is, for the time being, only to be used if present, thus my code would have to adapt into something like this:

Now I’m checking if the name parameter is present and if it is, I’m verifying the name matches, otherwise if it’s not present I’m also applying the function (like I did before).

This implementation is still valid for the old test cases I had, I can check that by running bit test on my terminal. These tests, as you can see here were only interested in the old behavior (no name is used).

Now I should also add a new test to ensure the new logic also works, so I can add something like this:

I am now using my traverse method to capture the color of my chairs. And Bit is telling me that it works:

$ bit test
- loading bit...
- loading aspects...
- running command test [pattern]...
testing total of 1 components in workspace 'my-workspace-name'
testing 1 components with environment teambit.harmony/node

PASS components/composite/composite.spec.ts
Composite component
√ should correctly add a component to the children list (2 ms)
√ should iterate over the full composite children-first (1 ms)
√ should iterate over the entire composite and apply the function only to the matching name node

Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.033 s
Ran all test suites.
tested 1 components in 3.655 seconds.
Test results for my independent component

I’m ready to release this version now. However, this is not my final version, as I started this journey trying to update the method into not supporting a full traversal anymore.

I can release a new version using Bit by typing:

$ bit tag --all 0.0.3
$ bit export

This will effectively release version 0.0.3 which is still backward compatible. After this release, I’ll allow for the Adoption phase to start.

The Adoption

This is when my users will adapt their code into supporting the new version while still having their current version work. Does that make any sense?

Essentially through the adoption phase, I’m allowing them to “future proof” their code. I can do this by showing them exactly what the new behavior will look like, without removing the old one.

During this phase I will also have to make sure I correctly communicate the future deprecation of the old behavior. I can do that through updates on the documentation, code comments or any other means of notification you can think of. However you choose to do it, remember that this is the time. You’re not just giving your users time to adapt, you’re also buying some time to properly notify them of the future breaking change.

Look at the new version of my docs for this release:

1*KKwkzXXeNeZbIi7bOLDd-g.png?q=20
versioning-your-components-through-the-expand-contract-pattern-bc31afaae623

For version 0.0.3 I’ve added a deprecation warning to make sure it’s clear for all users that my traverse method is going to change.

Once you’ve given them enough information and time to adjust, the “Contract” part begins.

The Contract phase

As you can probably imagine, this is where the code changes one more time, and now is when the actual breaking change is published. My example was very basic, but consider having a bigger set of parallel features to be maintained over time.

You can’t live in the Adoption phase for too long, you have to finish what you started, so in the end, you need to remove backward compatibility and clean up your code, API or whatever it is you’re publishing.

During the contract phase you’ll strip away all the extra code you added to support both versions. In my case, that looks like this:

The change is minimal in this example, but you can see how the signature of the method changed now to have the name attribute be mandatory (it didn’t change in the sense of adding extra attributes or anything, that would not be allowed at this point). And the code changed as well, the IF statement is now simpler, since the name attribute is now going to be present all the time.

In fact, with this new change, one of the tests will fail because we’re clearly no longer supporting the old version (calling traverse with only one parameter):

1*DrHZ8A3JT6Ojvp3FKfny3w.png?q=20
versioning-your-components-through-the-expand-contract-pattern-bc31afaae623

That code is no longer valid, so we need to either update it accordingly or remove the test altogether. Since I’m already testing this method on another test, I’ll remove this one.

And I’m now ready to publish the newest version. However, if you’re using a versioning scheme such as Semver remember to properly update the major version, since we’re definitely pushing a breaking change now.

In my case, since I’m using Bit, I can do that easily by running:

$ bit tag 1.0.0
$ bit export

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK