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).
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);
}
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);
}
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);
}
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:
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]
.
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 viathis
). 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
.