5 Services and Alternatives

Start Cloud IDE

Start Cloud IDE (Start Services)

So far, we worked exclusively on the presentation layer. We now introduce a further layer of so-called services to implement the actual business logic. The use of services is a procedural approach whereby individual service calls modify the business objects (entities), while these business objects do not contain any business logic themselves.

The individual service methods are grouped in service classes according to their theme. We will see in the following section, for example, how a CampaignService is introduced to process Campaign objects.

Other aspects can be added to the service calls in later iterations. They can be used to persist entities, establish transaction control and safeguard the security of the application.

Using CDI, we will focus exclusively on the structure of the service classes. As in previous chapters, we won’t pursue the approach of adding new functionalities to the application, but of modifying our target architecture. As we do so, we will learn a couple more important features of CDI.

For those wishing to learn more about the service pattern, we recommend the lecture Real World Java EE Patterns – Rethinking Best Practices.

5.1 Adding the Mock Method to a Service Class

Currently, we are using the method createMockCampaigns to create a list of sample campaigns in the bean CampaignListProducer. The method is invoked by the method init. We want to modify this such that the list of campaigns will be returned by a service call.

To do this, we will first create the interface CampaignService – from the package de.dpunkt.myaktion.services – which we will implement in the upcoming service class. The source code of the interface is shown in Listing 5-1.

package press.turngeek.mycampaign.services;

import press.turngeek.mycampaign.model.Campaign;
import java.util.List;

public interface CampaignService {
    List<Campaign> getAllCampaigns();
}

Listing 5-1 CampaignService interface

The interface of the service currently contains only one method, getAllCampaigns, whose purpose is to return the list of Campaign objects. It would technically be possible to implement the service directly, without an interface; however, since we want to use a mock implementation in addition to an implementation with real data, an interface is necessary.

To call the declared service, we must modify the init method of the CampaignListProducer bean as follows:

@Inject
private CampaignService campaignService;

@PostConstruct
public void init() {
    campaigns = campaignService.getAllCampaigns();
}

The annotation @Inject to add another bean of the type CampaignService as well as using the method init to invoke its method getAllCampaigns, the purpose being to obtain the list of Campaign objects. Now, it should hopefully be clear why we converted the constructor into a method with the annotation @PostConstruct in section 3.3.1; if we hadn’t done this, the CampaignService bean would not be available to us now.

If we start the application, we’ll see that there is still a small problem: since we have so far only defined the interface of the service, and have not yet implemented a service class, the container cannot find an implementation. When deployed, it therefore shows the error message: WELD-001408 Unsatisfied dependencies for type [CampaignService].

In light of this, we now require an implementation of the CampaignService. This implementation will contain only the method getAllCampaigns, which we will take from the createMockCampaigns method of the CampaignListProducer bean. The complete implementation is shown in Listing 5-2; the class MockCampaignServiceBean must be stored in the package press.turngeek.mycampaign.services. After restarting the application, the dependency will be correctly resolved, which means that CDI, using the MockCampaignServiceBean, will find the correct implementation of the bean CampaignService.

package press.turngeek.mycampaign.services;

import press.turngeek.mycampaign.model.Account;
import press.turngeek.mycampaign.model.Campaign;
import press.turngeek.mycampaign.model.Donation;

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Alternative;
import java.util.LinkedList;
import java.util.List;

@RequestScoped
public class MockCampaignServiceBean implements CampaignService {
    public List<Campaign> getAllCampaigns() {
		Donation donation1 = new Donation();
		donation1.setDonorName("John Smith");
		donation1.setAmount(20d);
		donation1.setReceiptRequested(true);
		donation1.setStatus(Donation.Status.TRANSFERRED);
		donation1.setAccount(new Account(donation1.getDonorName(), 
			"XXX Bank","DE44876543210000123456"));
		Donation donation2 = new Donation();
		donation2.setDonorName("Peter Jones");
		donation2.setAmount(30d);
		donation2.setReceiptRequested(false);
		donation2.setStatus(Donation.Status.IN_PROCESS);
		donation2.setAccount(new Account(donation2.getDonorName(), 
			"YYY Bank","DE44864275310000654321"));
		List<Donation> spenden = new LinkedList<>();
		spenden.add(donation1);
		spenden.add(donation2);
		Campaign campaign1 = new Campaign();
		campaign1.setName("Shirts for the U19 Team");
		campaign1.setTargetAmount(1000d);
		campaign1.setAmountDonatedSoFar(258d);
		campaign1.setDonationMinimum(20d);
		campaign1.setId(1L);
		campaign1.setAccount(new Account("Robert Cook", "ABC Bank",
			"DE44123456780100200300"));
		campaign1.setDonations(spenden);
		Campaign campaign2 = new Campaign();
		campaign2.setName("Wheelchair for Maria");
		campaign2.setTargetAmount(2500d);
		campaign2.setAmountDonatedSoFar(742d);
		campaign2.setDonationMinimum(25d);
		campaign2.setId(2L);
		campaign2.setAccount(campaign1.getAccount());
		campaign2.setDonations(spenden);
		List<Campaign> ret = new LinkedList<>();
		ret.add(campaign1);
		ret.add(campaign2);
		return ret;
	}
}

Listing 5-2 MockCampaignServiceBean class

5.2 Select the Service Implementation to Use with Qualifiers

Our sample campaigns from the mock implementation MockCampaignServiceBean are all well and good, but they do not fulfil the requirements of the application. For this reason, we will now write the CampaignServiceBean – another implementation of the interface CampaignService. Like the previous implementation, it will be stored in the package press.turngeek.mycampaign.services and can be found in Listing 5-3.

package press.turngeek.mycampaign.services;

import press.turngeek.mycampaign.model.Campaign;

import javax.enterprise.context.RequestScoped;
import java.util.LinkedList;
import java.util.List;

@RequestScoped
public class CampaignServiceBean implements CampaignService {

  @Override
  public List<Campaign> getAllCampaigns() {
     return new LinkedList<Campaign>();
  }

}

Listing 5-3 CampaignServiceBean class

This implementation returns only an empty list of Campaign objects. In later iterations we might add a delegation to a persistence layer here to retrieve the actual list of campaigns from the database. But for the time being, for explaining CDI, an empty list is totally sufficient.

If we start the application again, a fresh error message will appear: WELD-001409 Ambiguous dependencies for type [CampaignService].

In contrast to the previous section, we now have the problem that CDI finds multiple implementations of the CampaignService bean and cannot decide for itself which one is to be used.

As we did with the ambiguities in our messages (see section 4.2), we can resolve this problem using a qualifier. This solution is not required for our application, since we will tackle the issue using an alternative approach. However, if we wish to see how the qualifier solution works, we can test it out by creating the class TestQualifier with the qualifier @MyService from Listing 5-4 and saving it in the package press.turngeek.mycampaign.util.

package press.turngeek.mycampaign.util;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

public class TestQualifier {

    @Qualifier
    @Target({ FIELD, TYPE })
    @Retention(RUNTIME)
    public @interface MyService {
    }
}

Listing 5-4 TestQualifier class

On one end, the qualifier @MyService must be added to the attribute campaignService within the bean CampaignListProducer:

@Inject
@MyService
private CampaignService campaignService;

…and, on the other end, to the implementation of CampaignService that is to be used:

@RequestScoped
@MyService
public class CampaignServiceBean implements CampaignService {

If we start the application once more, we will see that the ambiguities have now been resolved and that the application uses the implementation CampaignServiceBean for the CampaignService. The class diagram in Fig. 5-1 illustrates the classes involved.

Relationships of the classes involved in the unambiguous injection of a bean
Fig. 5-1 Relationships of the classes involved in the unambiguous injection of a bean

We have now shown how ambiguities at the injection point can be resolved with the help of a qualifier. However, as mentioned above, we will not be using this approach for our application, and so the annotations and the class TestQualifier added in the last step should now be removed.

Instead of resolving the ambiguities with qualifiers, we can choose to use the annotation @Alternative from the package javax.enterprise.inject.

To follow this approach, we need simply to add this annotation to the bean MockCampaignServiceBean:

@RequestScoped
@Alternative
public class MockCampaignServiceBean implements CampaignService {

This annotation designates the bean as an alternative, non-default implementation. Instead of using this, the container will use the remaining implementation, CampaignServiceBean. Therefore we once again have a definitive resolution.

If we want to invoke an alternative implementation, we need to specify this explicitly in the CDI configuration file. We haven’t needed to do this so far, since we have relied solely on the standard configuration of CDI.

We will change this now for the sake of testing the invocation of an alternative implementation. Specifically, we will save the sample configuration from Listing 5-5 in the file WEB-INFbeans.xml. After the application has been restarted, the mock implementation will be used. This shows how simple it is to use different service implementations, regardless of the deployment scenario.

Since the implementation CampaignServiceBean is required for the upcoming iterations, we now need to delete the file WEB-INFbeans.xml, causing CDI to revert back to the standard configuration.

CDI and beans.xml

In CDI 1.0 (part of Java EE 6), the mere existence of the configuration file beans.xml – even when empty – was the switch to enable CDI for all classes of the application. Up until now, however, beans.xml has not existed in our project – and CDI has functioned anyway!

Since CDI Version 1.1 (part of Java EE 7), it has been possible, using beans.xml, to influence which classes of the application are recognized as CDI beans (attribute bean-discovery-mode). If an application does not possess a beans.xml file, CDI behaves as though the attribute bean-discovery-mode has been assigned the value annotated. In this case, the only classes recognized as CDI beans are those that have been explicitly assigned a scope annotation. If we assign the attribute the value all, we achieve the same behavior as in CDI 1.0. Further details on possible settings can be found in the CDI specification.

<?xml version="1.0" encoding="UTF-8"?>
<beans
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
  bean-discovery-mode="annotated">
  <alternatives>
    <class>press.turngeek.mycampaign.services.MockCampaignServiceBean</class>
  </alternatives>
</beans>

Listing 5-5 XML file beans.xml

In this section, we have explored two methods for resolving ambiguities: qualifiers and alternatives. The two are used in different cases:

Qualifiers are used when multiple implementations of the same type are used during the runtime of the application. During the development stage, the programmer uses qualifiers to determine definitively which implementation should be used.

Alternatives are different in that implementations are defined per deployment. This has the result that only one implementation exists at runtime.

We have now completed the development part of our CDI tutorial! We recommend you compare your results with the snapshot of the project in our Cloud IDE:

Start Cloud IDE

Start Cloud IDE (End Services)

Discussion

Use the message board below to give the authors your feedback or to discuss this page’s topic with other readers (in English please!). Please don’t expect the authors to answer directly, but they might update the content of this site according to your feedback.