Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the appropriate way to plan for a Java API with new features over time?

I'm working with a team on a new Java API for one of our internal projects. We probably won't be able to take the time to stop and hash out all the details of the Java interfaces and get them 100% perfect at the beginning.

We have some core features that have to be there up front, and others that are likely to be added later over time but aren't important now, + taking the time to design those features now is a luxury we don't have. Especially since we don't have enough information yet to get all the design details right.

The Java approach to APIs is that once you publish an interface, it's effectively immutable and you should never change it.

Is there a way to plan for API evolution over time? I've read this question and I suppose we could do this:

// first release
interface IDoSomething
{
    public void hop();
    public void skip();
    public void jump();
}

// later
interface IDoSomething2 extends IDoSomething
{
    public void waxFloor(Floor floor);
    public void topDessert(Dessert dessert);
}

// later still
interface IDoSomething3 extends IDoSomething2
{
    public void slice(Sliceable object);
    public void dice(Diceable object);
}

and then upgrade our classes from supporting IDoSomething to IDoSomething2 and then IDoSomething3, but this seems to have a code smell issue.

Then I guess there's the Guava way of marking interfaces with @Beta so applications can use these at risk, prior to being frozen, but I don't know if that's right either.

like image 685
Jason S Avatar asked Oct 08 '14 16:10

Jason S


People also ask

Can we develop API in Java?

You can use the Java Client API to quickly become productive in your existing Java environment, using the Java interfaces for search and document management. You can also use the Java Client API extension capability to invoke XQuery and Server-Side JavaScript code on MarkLogic Server.


3 Answers

If you want flexible code generics can help.

For example, instead of:

interface FloorWaxer
{
    public void waxFloor(Floor floor);
}

You can have:

interface Waxer<T> 
{
    void wax(T t);
}

class FloorWaxer implements Waxer<Floor> 
{
    void wax(Floor floor);
}

Also, Java 8 brought default methods in interfaces which allow you to add methods in already existing interfaces; with this in mind you can make you interfaces generic. This means you should make your interfaces as generic as possible; instead of:

interface Washer<T>
{
    void wash(T what);   
}

and then to later add

interface Washer<T>
{
    void wash(T what);   
    void wash(T what, WashSubstance washSubstance); 
}

and later add

interface Washer<T>
{
    void wash(T what);   
    void wash(T what, WashSubstance washSubstance); 
    void wash(T what, WashSubstance washSubstance, Detergent detergent); 
}

you can add from the beginning

@FunctionalInterface
interface Washer<T>
{
    void wash(T what, WashSubstance washSubstance, Detergent detergent); 

    default wash(T what, WashSubstance washSubstance) 
    {
        wash(what, washSubstance, Detergent.DEFAULT_DETERGENT);
    }

    default wash(T what, Detergent detergent) 
    {
        wash(what, WashSubstance.DEFAULT_WASH_SUBSTANCE, detergent);
    }

    default wash(T what) 
    {
        wash(what, WashSubstance.DEFAULT_WASH_SUBSTANCE, Detergent.DEFAULT_DETERGENT);
    }
}

Also, try to make your interfaces functional (only one abstract method) so you can benefit from lambdas sugaring.

like image 98
Random42 Avatar answered Sep 28 '22 08:09

Random42


You could take the approach that tapestry-5 has taken which it dubs "Adaptive API" (more info here).

Instead of locked down interfaces, tapestry uses annotations and pojo's. I'm not entirely sure of your circumstances but this may or may not be a good fit. Note that tapestry uses ASM (via plastic) under the hood so that there is no runtime reflection to achieve this.

Eg:

public class SomePojo {
   @Slice
   public void slice(Sliceable object) {
      ...
   }

   @Dice
   public void dice(Diceable object) {
      ...
   }
}

public class SomeOtherPojo {
   @Slice
   public void slice(Sliceable object) {
      ...
   }

   @Hop
   public void hop(Hoppable object) {
      ...
   }
}
like image 31
lance-java Avatar answered Sep 28 '22 07:09

lance-java


You could use a new package name for the new version of API - this would allow old and new API live side-by-side and API users can convert their components to the new API one at a time. You can provide some adaptors to help them with heavy-lifting on boundaries where objects get passed across boundaries between classes using new and old API.

The other option is quite harsh but could work for internal project - just change what you need and make users to adapt.

If you are just adding, providing default implementation (in an abstract class) of the new methods can make the process smoother. Of course this is not always applicable.

Signalling the change by changing major version number a provide detailed documentation about how to upgrade the code base to the new version of API is good idea in both cases.

like image 34
Rostislav Matl Avatar answered Sep 28 '22 09:09

Rostislav Matl