Skip to content

Latest commit

 

History

History
149 lines (109 loc) · 6.25 KB

guide-usecase.asciidoc

File metadata and controls

149 lines (109 loc) · 6.25 KB

UseCase

ATTENTION: You are on the develop branch. This has been renamed to master. The develop branch will not be maintained anymore. It is only left here to avoid broken links to existing content. Please update links to point to the master branch. For details look at issue #320.

A use-case is a small unit of the logic layer responsible for an operation on a particular entity (business object). It is defined by an interface (API) with its according implementation. Following our architecture-mapping, use-cases are named Uc«Operation»«BusinessObject»[Impl]. The prefix Uc stands for use-case and allows to easily find and identify them in your IDE. The «Operation» stands for a verb that is operated on the entity identified by «BusinessObject». For CRUD we use the standard operations Find and Manage that can be generated by CobiGen. This also separates read and write operations (e.g. if you want to do CQSR, or to configure read-only transactions for read operations).

Find

The UcFind«BusinessObject» defines all read operations to retrieve and search the «BusinessObject». Here is an example:

public interface UcFindBooking {

  BookingEto findBooking(Long id);

  BookingCto findBookingCto(Long id);

  Page<BookingEto> findBookingEtos(BookingSearchCriteriaTo criteria);

  Page<BookingCto> findBookingCtos(BookingSearchCriteriaTo criteria);

}

Manage

The UcManage«BusinessObject» defines all CRUD write operations (create, update and delete) for the «BusinessObject». Here is an example:

public interface UcManageBooking {

  BookingEto saveBooking(BookingEto booking);

  boolean deleteBooking(Long id);

}

Custom

Any other non CRUD operation Uc«Operation»«BusinessObject» uses any other custom verb for «Operation». Typically, such custom use-cases only define a single method. Here is an example:

public interface UcApproveBooking {

  void approveBooking(BookingEto booking);

}

Implementation

For the implementation of a use-cas, the same rules that are described for the component-facade implementation.

.

However, when following the use-case approach, your component facade simply changes to:

public interface Bookingmanagement extends UcFindBooking, UcManageBooking, UcApproveBooking {
}

Where the implementation only delegates to the use-cases and gets entirely generated by CobiGen:

public class BookingmanagementImpl implements  {

  @Inject
  private UcFindBooking ucFindBooking;

  @Inject
  private UcManageBooking ucManageBooking;

  @Inject
  private UcApproveBooking ucApproveBooking;

  @Override
  public BookingEto findBooking(Long id) {
    return this.ucFindBooking.findBooking(id);
  }

  @Override
  public Page<BookingEto> findBookingEtos(BookingSearchCriteriaTo criteria) {
    return this.ucFindBooking.findBookingEtos(criteria);
  }

  @Override
  public BookingEto saveBooking(BookingEto booking) {
    return this.ucManageBooking.saveBooking(booking);
  }

  @Override
  public boolean deleteBooking(Long id) {
    return this.ucManageBooking.deleteBooking(booking);
  }

  @Override
  public void approveBooking(BookingEto booking) {
    this.ucApproveBooking.approveBooking(booking);
  }

  ...
}

This approach is also illustrated by the following UML diagram:

Component facade with use cases.

Internal use case

Sometimes, a component with multiple related entities and many use-cases needs to reuse business logic internally. Of course, this can be exposed as an official use-case API but this will imply using transfer-objects (ETOs) instead of entities. In some cases, this is undesired e.g. for better performance to prevent unnecessary mapping of entire collections of entities. In the first place, you should try to use abstract base implementations providing reusable methods the actual use-case implementations can inherit from. If your business logic is even more complex and you have multiple aspects of business logic to share and reuse but also run into multi-inheritance issues, you may also just create use-cases that have their interface located in the impl scope package right next to the implementation (or you may just skip the interface). In such a case, you may define methods that directly take or return entity objects. To avoid confusion with regular use-cases, we recommend to add the Internal suffix to the type name leading to Uc«Operation»«BusinessObject»Internal[Impl].

Injection issues

Technically, now you have two implementations of your use-case:

  • the direct implementation of the use-case (Uc*Impl)

  • the component facade implementation («Component»Impl)

When injecting a use-case interface this could cause ambiguities. This is addressed as following:

  • In the component facade implementation («Component»Impl) spring is smart enough to resolve the ambiguity as it assumes that a spring bean never wants to inject itself (it can already be an access via this). Therefore, only the proper use-case implementation remains as a candidate and injection works as expected.

  • In all other places, simply always inject the component facade interface instead of the use-case.

In case you might have the lucky occasion to hit this nice exception:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'uc...Impl': Bean with name 'uc...Impl' has been injected into other beans [...Impl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

To get rid of such an error you need to annotate your according implementation also with @Lazy in addition to @Named.