Han_Chen
Han_Chen

Reputation: 305

Make HTTP request to itself: reusing endpoint

I have two API's in Jersey with one potentially using the other one.

class PersonAPI{
    @PATH("/person")
    @GET
    // support query param filter such as ?first_name=John&birthday=1988-09-12
    public Person getPerson(){
        return PersonService.getPerson(...);
    }
}


// some other class
class PetAPI{
    @PATH("/pet")
    @GET
    // support query param such as ?owner.first_name=John&owner.birthday=1988-09-12
    public Pet getPet(){
        //filter person based on "owner." params 

    }
}

One way to implement filtering person in PetAPI is to access PersonService, but I would rather PetAPI to use PersonAPI via HTTP Request as if PersonAPI is another resource. This is because PetAPI may in the future be refactored to another container, and won't be in the same application as PersonAPI.

Is there a way in Jersey to call PersonAPI like this, even though for now PersonAPI and PetAPI are in the same application?

WebTarget target = client.target(baseURL + "/person");
target.queryParam( .... );
target.request().get().readEntity(Person.class);

Edit: I guess the more important question is: is this a good approach when handling one resource needing to use another resource?

Upvotes: 1

Views: 1463

Answers (1)

Rob
Rob

Reputation: 6507

The situation you describe is pretty common and is not specific to Jersey in any way.

Without doubt, you do not want to be making web service calls between APIs in the same container. That is just inefficient. You really want a solution that is efficient when the APIs are in the same container and is easy to refactor if/when they are housed in different containers.

A pattern that works well with a service-oriented project is to define an interface for the service, and then have two different implementations of that interface. The first implementation is the one you already have and contains the actual business logic. The second implementation is used when the business logic is actually implemented in another container. To be concrete, your interface might be called PersonService and it is implemented by PersonServiceImpl and PersonRemoteServiceImpl (or whatever naming convention you like).

PersonServiceImpl.getPerson() queries the DB (or however it gets a person).

PersonRemoteServiceImpl.getPerson() makes and HTTP call to the other container to get the person.

For now, with PetApi and PersonApi housed in the same container, you don't even need to implement PersonRemoteServiceImpl (yet) - save that refactor until you need it. But when you do the refactor, you just have to create the "remote" implementation of the service and replace which concrete implementation of the service you use in PetApi. In fact, you can put off creating the separate interface for the service until you need a second implementation.

If you want to make this approach a bit easier, you will probably want to not implement your service methods as static (like your example code shows). The problem is that you have to mention the actual class, rather than being able to switch it to an interface. A coding pattern like this is what I am suggesting:

public interface PersonService {
    Person getPerson();
}

public class PersonServiceImpl implements PersonService {
    public Person getPerson() { return new Person(); }
}

public class PersonApi {
    @PATH("/person")
    @GET
    public Person getPerson(){
        return getPersonService().getPerson(...);
    }

    public PersonService getPersonService() {
        // Return the appropriate implementation.
        if (personService == null) {
            personService = new PersonServiceImpl();
        }
        return personService
    }
    private PersonService personService;
}

The example management of the service implementation instance is naive and it would be better to either implement some sort of "service manager" that will share a single instance among all consumers of that service, or a framework like Spring that implements IOC (inversion of control) to inject the desired service implementation.

As mentioned above, (unless you are using one of the frameworks) you can put off separating the interface from the implementation until you actually need that. If you plan ahead, that refactor is easy to localize if/when you actually need it.

Upvotes: 1

Related Questions