'. '

APISeam

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(New page: Do you maintain an API and have you ever received a patch from an external contributor which you considered inappropriate? I did, many times. Also I was many times in a position when I...)
Current revision (07:50, 30 October 2011) (edit) (undo)
m (CodeInjection moved to APISeam: Code injection term is already used for hacking - like sending misformated strings to execute special SQL queries. Let's try to use term APISeam.)
 
(9 intermediate revisions not shown.)
Line 1: Line 1:
-
Do you maintain an [[API]] and have you ever received a patch from an external contributor which you considered inappropriate? I did, many times. Also I was many times in a position when I wanted to twist some [[API]] in ways that were not really comfortable for the original maintainer.
+
Do you maintain some [[API]]? Have you ever received a patch from an external contributor which you considered inappropriate? I did, many times. Also I was many times in a position when I wanted to twist some other [[API]] in ways that were not really comfortable for the original [[API]] maintainer.
-
What can be result of such clash? Rejection of the patch? Possible, but that probably upsets the submitter and good [[API]] maintainer does not want to upset those who are willing to contribute patches. Accepting silly patch? Well, that creates a maintenance nightmare. No [[API]] maintainer is wishing to spend time bugfixing, expanding and baby sitting an unwanted code. Looks like loose/loose situation! Is there a better solution?
+
What can be result of such conflict of interests? Rejection of the patch? Possibly, but that probably upsets the submitter and a good [[API]] maintainer does not want to upset those who are willing to contribute patches. Accepting a silly patch? Well, that creates a maintenance nightmare. No [[API]] maintainer is wishing to spend time bugfixing, expanding and baby sitting an unwanted code. Looks like lose/lose situation! Is there a better solution?
== Code Injection ==
== Code Injection ==
-
The solution is to create a ''code slot''. Instead of accepting a an unwanted patch, the [[API]] can be extended to execute arbitrary code at given ''patch set'' point. Imagine simple [[API]] for ''counting down'' to zero:
+
The solution is to create a ''code slot''. Instead of accepting an unwanted patch, the [[API]] can be extended to execute arbitrary code at given ''patch set'' point. Imagine simple [[API]] for ''counting down'' to zero:
<source lang="java" snippet="codeinjection.CountDown"/>
<source lang="java" snippet="codeinjection.CountDown"/>
Line 13: Line 13:
<source lang="java" snippet="codeinjection.v1"/>
<source lang="java" snippet="codeinjection.v1"/>
-
It is obvious to author of the [[API]] and almost everyone else that '''down()''' means decrement by one. But imagine that there is someone who would like to decrement by two. Such person may provide a patch, but maybe it is complicated, maybe it contains code that the [[API]] maintainer does not want to accept. Still, if the maintainer wants to let that submitter ''shot himself into his own foot'', he can open his [[API]] by providing ''code slot'' interface:
+
It is obvious to author of the [[API]] and almost everyone else that '''down()''' means decrement by one. Just like in this test:
 +
 
 +
<source lang="java" snippet="codeinjection.fourtimes"/>
 +
 
 +
But imagine that there is someone who would like to decrement by two. Such person may even provide a patch, but the patch needs to complicate things. As such the maintainer may not want to accept it. Still, if the maintainer agrees that under some circumstances, the submitter shall have right to ''shot himself into his own foot'', the [[API]] can be opened up by providing ''code slot'' interface:
<source lang="java" snippet="codeinjection.slot"/>
<source lang="java" snippet="codeinjection.slot"/>
Line 21: Line 25:
<source lang="java" snippet="codeinjection.v2"/>
<source lang="java" snippet="codeinjection.v2"/>
-
This keeps the [[API]] clean of any hacks of subtleties of the foreign code. The [[API]] just defines a ''code slot'' by use of '''ServiceLoader.load''', which is standard [[JDK]] 1.6 interface for doing [[ComponentInjection]]. Either it is present (someone included additional [[JAR]] on classpath with registration of implementation of the interface), and in such way the decrement is completely handled by new code, or it is missing and then the behaviour remains the same as in version 1.0.
+
This keeps the [[API]] clean of any hacks of subtleties of the foreign code. The [[API]] just defines a ''code slot'' by use of '''ServiceLoader.load''', which is standard [[JDK]] 1.6 interface for doing [[Component Injection]]. Either it is present (someone included additional [[JAR]] on classpath with registration of implementation of the interface), and in such way the decrement is completely handled by new code, or it is missing and then the behaviour remains the same as in version 1.0. If no implementation of the ''slot'' is around, things remain the same as in the first test case. Once some implementation of extender (like the '''DecrementByTwo''' class) is registered, behaviour changes:
 +
 
 +
<source lang="java" snippet="codeinjection.twice"/>
 +
 
 +
''Code slots'' provide a nice balance between the need to extend an [[API]] with additional functionality and the fear of maintaining such code. Every [[API]] can be extended to provide ''code slots'' and I see them as a perfect way of providing patches for ''unwanted'' behaviour.
 +
 
 +
For example I want to change [[Felix]] to better cooperate with [[NetBeans]] runtime container. Of course I do not want to pollute the original [[Felix]] code with anything [[NetBeans]] specific, but maybe all I need is just a single ''code slot'' to hook in.
-
''Code slots'' provide a nice balance between the need to extend an [[API]] with additional functionality and the fear of maintaining such code. Every [[API]] can be extended to provide ''code slots'' and I see them as a perfect way of providing patches for ''unwanted'' behaviour. For example I want to change [[Felix]] to cooperating with [[NetBeans]] runtime container. Of course I do not want to polute the original [[Felix]] code with anything [[NetBeans]] specific, but maybe all I need is just a single ''code slot'' to hook in. Of course, the [[API]] maintainer needs to be ready to support such ''slot'' and accept any, even surprising behaviour inside it, so the solution is not completely for free. Still, it is the one that provides the best balance by turning an unacceptable patch into acceptable slot.
+
Of course, the [[API]] maintainer needs to be ready to support such ''slot'' and accept any, even surprising behaviour inside it, so the solution is not completely for free. It is still desirable to evaluate the patch with techniques discussed in [[Teamwork|chapter 16]]. Yet, use of ''code slots'' seems to provide the best balance by turning an unacceptable patch into acceptable slot.
 +
<comments/>
[[Category:APIDesignPatterns]]
[[Category:APIDesignPatterns]]

Current revision

Do you maintain some API? Have you ever received a patch from an external contributor which you considered inappropriate? I did, many times. Also I was many times in a position when I wanted to twist some other API in ways that were not really comfortable for the original API maintainer.

What can be result of such conflict of interests? Rejection of the patch? Possibly, but that probably upsets the submitter and a good API maintainer does not want to upset those who are willing to contribute patches. Accepting a silly patch? Well, that creates a maintenance nightmare. No API maintainer is wishing to spend time bugfixing, expanding and baby sitting an unwanted code. Looks like lose/lose situation! Is there a better solution?

Code Injection

The solution is to create a code slot. Instead of accepting an unwanted patch, the API can be extended to execute arbitrary code at given patch set point. Imagine simple API for counting down to zero:

Code from CountDown.java:
See the whole file.

public abstract class CountDown {
    CountDown() {
    }
 
    public static CountDown create(int initial) {
        return createSimpleImplementation(initial);
    }
 
    /** Decrements the counter */
    public abstract void down();
    /** @return true if the counter is 0 or less */
    public abstract boolean isDown();
}
 

and its simple implementation:

Code from CountDownImplV1.java:
See the whole file.

private int cnt;
public void down() {
    cnt--;
}
 
public boolean isDown() {
    return cnt <= 0;
}
 

It is obvious to author of the API and almost everyone else that down() means decrement by one. Just like in this test:

Code from Version10Test.java:
See the whole file.

@Test
public void testDecrementFourTimes() {
    CountDown counter = create(4);
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertTrue("Down now", counter.isDown());
}
 

But imagine that there is someone who would like to decrement by two. Such person may even provide a patch, but the patch needs to complicate things. As such the maintainer may not want to accept it. Still, if the maintainer agrees that under some circumstances, the submitter shall have right to shot himself into his own foot, the API can be opened up by providing code slot interface:

Code from CountDownExtender.java:
See the whole file.

public interface CountDownExtender {
    /** Allows providers to specify that it means to "decrement" a value.
     *
     * @param value previous value
     * @return result of "--" operation in interpretation of this extender
     */
    public int decrement(int value);
}
 

and looking up its implementation at the right place:

Code from CountDownImplV2.java:
See the whole file.

public void down() {
    Iterator<CountDownExtender> it =
        ServiceLoader.load(CountDownExtender.class).iterator();
    if (it.hasNext()) {
        // injected behaviour
        cnt = it.next().decrement(cnt);
    } else {
        // common behaviour of 1.0 version
        cnt--;
    }
}
 
public boolean isDown() {
    return cnt <= 0;
}
 

This keeps the API clean of any hacks of subtleties of the foreign code. The API just defines a code slot by use of ServiceLoader.load, which is standard JDK 1.6 interface for doing Component Injection. Either it is present (someone included additional JAR on classpath with registration of implementation of the interface), and in such way the decrement is completely handled by new code, or it is missing and then the behaviour remains the same as in version 1.0. If no implementation of the slot is around, things remain the same as in the first test case. Once some implementation of extender (like the DecrementByTwo class) is registered, behaviour changes:

Code from Version20Test.java:
See the whole file.

@Test
public void testDecrementTwoTimesEnough() {
    MockServices.setServices(DecrementByTwo.class);
    CountDown counter = create(4);
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertFalse("Not down yet", counter.isDown()); counter.down();
    assertTrue("Two Down is enough", counter.isDown());
}
 
public static final class DecrementByTwo implements CountDownExtender {
    public int decrement(int value) {
        return value - 2;
    }
}
 

Code slots provide a nice balance between the need to extend an API with additional functionality and the fear of maintaining such code. Every API can be extended to provide code slots and I see them as a perfect way of providing patches for unwanted behaviour.

For example I want to change Felix to better cooperate with NetBeans runtime container. Of course I do not want to pollute the original Felix code with anything NetBeans specific, but maybe all I need is just a single code slot to hook in.

Of course, the API maintainer needs to be ready to support such slot and accept any, even surprising behaviour inside it, so the solution is not completely for free. It is still desirable to evaluate the patch with techniques discussed in chapter 16. Yet, use of code slots seems to provide the best balance by turning an unacceptable patch into acceptable slot.

<comments/>

Personal tools
buy