Compare commits

...

3 Commits

Author SHA1 Message Date
Subhrodip Mohanta
a163a46e03
Merge branch 'master' into business-delegate-explanation 2021-03-22 11:46:53 +05:30
Subhrodip Mohanta
964f2c56ad
Merge branch 'master' into business-delegate-explanation 2021-03-22 11:18:16 +05:30
Ilkka Seppälä
e0f1376bd0
#590 refactor and add explanation for business delegate 2021-03-20 21:37:53 +02:00
14 changed files with 220 additions and 283 deletions

View File

@ -9,21 +9,156 @@ tags:
---
## Intent
The Business Delegate pattern adds an abstraction layer between
presentation and business tiers. By using the pattern we gain loose coupling
between the tiers and encapsulate knowledge about how to locate, connect to,
and interact with the business objects that make up the application.
## Explanation
Real world example
> A mobile phone application promises to stream any movie in existence to your phone. It captures
> the user's search string and passes this on to the business delegate. The business delegate
> selects the most suitable video streaming service and plays the video from there.
In Plain Words
> Business delegate adds an abstraction layer between the presentation and business tiers.
Wikipedia says
> Business delegate is a Java EE design pattern. This pattern is directing to reduce the coupling
> in between business services and the connected presentation tier, and to hide the implementation
> details of services (including lookup and accessibility of EJB architecture). Business delegates
> acts as an adaptor to invoke business objects from the presentation tier.
**Programmatic Example**
First, we have an abstraction for video streaming services and a couple of implementations.
```java
public interface VideoStreamingService {
void doProcessing();
}
@Slf4j
public class NetflixService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("NetflixService is now processing");
}
}
@Slf4j
public class YouTubeService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("YouTubeService is now processing");
}
}
```
Then we have a lookup service that decides which video streaming service is used.
```java
@Setter
public class BusinessLookup {
private NetflixService netflixService;
private YouTubeService youTubeService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return youTubeService;
}
}
}
```
The business delegate uses a business lookup to route movie playback requests to a suitable
video streaming service.
```java
@Setter
public class BusinessDelegate {
private BusinessLookup lookupService;
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
}
```
The mobile client utilizes business delegate to call the business tier.
```java
public class MobileClient {
private final BusinessDelegate businessDelegate;
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
}
```
Finally, we can show the full example in action.
```java
public static void main(String[] args) {
// prepare the objects
var businessDelegate = new BusinessDelegate();
var businessLookup = new BusinessLookup();
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
// create the client and use the business delegate
var client = new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
```
Here is the console output.
```
21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing
21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing
```
## Class diagram
![alt text](./etc/business-delegate.png "Business Delegate")
![alt text](./etc/business-delegate.urm.png "Business Delegate")
## Related patterns
* [Service locator pattern](https://java-design-patterns.com/patterns/service-locator/)
## Applicability
Use the Business Delegate pattern when
* you want loose coupling between presentation and business tiers
* you want to orchestrate calls to multiple business services
* you want to encapsulate service lookups and service calls
* You want loose coupling between presentation and business tiers
* You want to orchestrate calls to multiple business services
* You want to encapsulate service lookups and service calls
## Tutorials
* [Business Delegate Pattern at TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm)
## Credits
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,136 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
<class id="1" language="java" name="com.iluwatar.business.delegate.BusinessDelegate" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="272" y="219"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="2" language="java" name="com.iluwatar.business.delegate.Client" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/Client.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="272" y="64"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<enumeration id="3" language="java" name="com.iluwatar.business.delegate.ServiceType" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/ServiceType.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="89" y="383"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</enumeration>
<interface id="4" language="java" name="com.iluwatar.business.delegate.BusinessService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="630" y="365"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</interface>
<class id="5" language="java" name="com.iluwatar.business.delegate.JmsService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/JmsService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="489" y="210"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="6" language="java" name="com.iluwatar.business.delegate.BusinessLookup" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="360" y="399"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="7" language="java" name="com.iluwatar.business.delegate.EjbService" project="business-delegate"
file="/business-delegate/src/main/java/com/iluwatar/business/delegate/EjbService.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="668" y="210"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<realization id="8">
<end type="SOURCE" refId="7"/>
<end type="TARGET" refId="4"/>
</realization>
<association id="9">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="10" name="lookupService">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="11" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="6" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="12">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="13" name="serviceType">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="14" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="3" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<realization id="15">
<end type="SOURCE" refId="5"/>
<end type="TARGET" refId="4"/>
</realization>
<association id="16">
<end type="SOURCE" refId="2" navigable="false">
<attribute id="17" name="businessDelegate">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="18" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="1" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<association id="19">
<end type="SOURCE" refId="1" navigable="false">
<attribute id="20" name="businessService">
<position height="0" width="0" x="0" y="0"/>
</attribute>
<multiplicity id="21" minimum="0" maximum="1">
<position height="0" width="0" x="0" y="0"/>
</multiplicity>
</end>
<end type="TARGET" refId="4" navigable="true"/>
<display labels="true" multiplicity="true"/>
</association>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -5,53 +5,42 @@ package com.iluwatar.business.delegate {
+ main(args : String[]) {static}
}
class BusinessDelegate {
- businessService : BusinessService
- lookupService : BusinessLookup
- serviceType : ServiceType
+ BusinessDelegate()
+ doTask()
+ setLookupService(businessLookup : BusinessLookup)
+ setServiceType(serviceType : ServiceType)
+ playbackMovie(movie : String)
+ setLookupService(lookupService : BusinessLookup)
}
class BusinessLookup {
- ejbService : EjbService
- jmsService : JmsService
- netflixService : NetflixService
- youTubeService : YouTubeService
+ BusinessLookup()
+ getBusinessService(serviceType : ServiceType) : BusinessService
+ setEjbService(ejbService : EjbService)
+ setJmsService(jmsService : JmsService)
+ getBusinessService(movie : String) : VideoStreamingService
+ setNetflixService(netflixService : NetflixService)
+ setYouTubeService(youTubeService : YouTubeService)
}
interface BusinessService {
class MobileClient {
- businessDelegate : BusinessDelegate
+ MobileClient(businessDelegate : BusinessDelegate)
+ playbackMovie(movie : String)
}
class NetflixService {
- LOGGER : Logger {static}
+ NetflixService()
+ doProcessing()
}
interface VideoStreamingService {
+ doProcessing() {abstract}
}
class Client {
- businessDelegate : BusinessDelegate
+ Client(businessDelegate : BusinessDelegate)
+ doTask()
}
class EjbService {
class YouTubeService {
- LOGGER : Logger {static}
+ EjbService()
+ YouTubeService()
+ doProcessing()
}
class JmsService {
- LOGGER : Logger {static}
+ JmsService()
+ doProcessing()
}
enum ServiceType {
+ EJB {static}
+ JMS {static}
+ valueOf(name : String) : ServiceType {static}
+ values() : ServiceType[] {static}
}
}
BusinessLookup --> "-ejbService" EjbService
BusinessDelegate --> "-serviceType" ServiceType
Client --> "-businessDelegate" BusinessDelegate
BusinessDelegate --> "-businessService" BusinessService
BusinessLookup --> "-netflixService" NetflixService
BusinessLookup --> "-youTubeService" YouTubeService
MobileClient --> "-businessDelegate" BusinessDelegate
BusinessDelegate --> "-lookupService" BusinessLookup
BusinessLookup --> "-jmsService" JmsService
EjbService ..|> BusinessService
JmsService ..|> BusinessService
NetflixService ..|> VideoStreamingService
YouTubeService ..|> VideoStreamingService
@enduml

View File

@ -33,9 +33,9 @@ package com.iluwatar.business.delegate;
* retrieved through service lookups. The Business Delegate itself may contain business logic too
* potentially tying together multiple service calls, exception handling, retrying etc.
*
* <p>In this example the client ({@link Client}) utilizes a business delegate (
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
* service and makes the service call.
* <p>In this example the client ({@link MobileClient}) utilizes a business delegate (
* {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate
* then selects the appropriate service and makes the service call.
*/
public class App {
@ -46,18 +46,16 @@ public class App {
*/
public static void main(String[] args) {
// prepare the objects
var businessDelegate = new BusinessDelegate();
var businessLookup = new BusinessLookup();
businessLookup.setEjbService(new EjbService());
businessLookup.setJmsService(new JmsService());
businessLookup.setNetflixService(new NetflixService());
businessLookup.setYouTubeService(new YouTubeService());
businessDelegate.setLookupService(businessLookup);
businessDelegate.setServiceType(ServiceType.EJB);
var client = new Client(businessDelegate);
client.doTask();
businessDelegate.setServiceType(ServiceType.JMS);
client.doTask();
// create the client and use the business delegate
var client = new MobileClient(businessDelegate);
client.playbackMovie("Die Hard 2");
client.playbackMovie("Maradona: The Greatest Ever");
}
}

View File

@ -23,24 +23,18 @@
package com.iluwatar.business.delegate;
import lombok.Setter;
/**
* BusinessDelegate separates the presentation and business tiers.
*/
@Setter
public class BusinessDelegate {
private BusinessLookup lookupService;
private ServiceType serviceType;
public void setLookupService(BusinessLookup businessLookup) {
this.lookupService = businessLookup;
}
public void setServiceType(ServiceType serviceType) {
this.serviceType = serviceType;
}
public void doTask() {
BusinessService businessService = lookupService.getBusinessService(serviceType);
businessService.doProcessing();
public void playbackMovie(String movie) {
VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie);
videoStreamingService.doProcessing();
}
}

View File

@ -23,6 +23,7 @@
package com.iluwatar.business.delegate;
import java.util.Locale;
import lombok.Setter;
/**
@ -31,21 +32,21 @@ import lombok.Setter;
@Setter
public class BusinessLookup {
private EjbService ejbService;
private NetflixService netflixService;
private JmsService jmsService;
private YouTubeService youTubeService;
/**
* Gets service instance based on service type.
* Gets service instance based on given movie search string.
*
* @param serviceType Type of service instance to be returned.
* @param movie Search string for the movie.
* @return Service instance.
*/
public BusinessService getBusinessService(ServiceType serviceType) {
if (serviceType.equals(ServiceType.EJB)) {
return ejbService;
public VideoStreamingService getBusinessService(String movie) {
if (movie.toLowerCase(Locale.ROOT).contains("die hard")) {
return netflixService;
} else {
return jmsService;
return youTubeService;
}
}
}

View File

@ -24,17 +24,17 @@
package com.iluwatar.business.delegate;
/**
* Client utilizes BusinessDelegate to call the business tier.
* MobileClient utilizes BusinessDelegate to call the business tier.
*/
public class Client {
public class MobileClient {
private final BusinessDelegate businessDelegate;
public Client(BusinessDelegate businessDelegate) {
public MobileClient(BusinessDelegate businessDelegate) {
this.businessDelegate = businessDelegate;
}
public void doTask() {
businessDelegate.doTask();
public void playbackMovie(String movie) {
businessDelegate.playbackMovie(movie);
}
}

View File

@ -26,13 +26,13 @@ package com.iluwatar.business.delegate;
import lombok.extern.slf4j.Slf4j;
/**
* Service EJB implementation.
* NetflixService implementation.
*/
@Slf4j
public class EjbService implements BusinessService {
public class NetflixService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("EjbService is now processing");
LOGGER.info("NetflixService is now processing");
}
}

View File

@ -1,33 +0,0 @@
/*
* The MIT License
* Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.business.delegate;
/**
* Enumeration for service types.
*/
public enum ServiceType {
EJB,
JMS
}

View File

@ -24,9 +24,9 @@
package com.iluwatar.business.delegate;
/**
* Interface for service implementations.
* Interface for video streaming service implementations.
*/
public interface BusinessService {
public interface VideoStreamingService {
void doProcessing();
}

View File

@ -26,13 +26,13 @@ package com.iluwatar.business.delegate;
import lombok.extern.slf4j.Slf4j;
/**
* Service JMS implementation.
* YouTubeService implementation.
*/
@Slf4j
public class JmsService implements BusinessService {
public class YouTubeService implements VideoStreamingService {
@Override
public void doProcessing() {
LOGGER.info("JmsService is now processing");
LOGGER.info("YouTubeService is now processing");
}
}

View File

@ -26,25 +26,20 @@ package com.iluwatar.business.delegate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* The Business Delegate pattern adds an abstraction layer between the presentation and business
* tiers. By using the pattern we gain loose coupling between the tiers. The Business Delegate
* encapsulates knowledge about how to locate, connect to, and interact with the business objects
* that make up the application.
*
* <p>Some of the services the Business Delegate uses are instantiated directly, and some can be
* retrieved through service lookups. The Business Delegate itself may contain business logic too
* potentially tying together multiple service calls, exception handling, retrying etc.
* Tests for the {@link BusinessDelegate}
*/
class BusinessDelegateTest {
private EjbService ejbService;
private NetflixService netflixService;
private JmsService jmsService;
private YouTubeService youTubeService;
private BusinessDelegate businessDelegate;
@ -54,19 +49,19 @@ class BusinessDelegateTest {
*/
@BeforeEach
public void setup() {
ejbService = spy(new EjbService());
jmsService = spy(new JmsService());
netflixService = spy(new NetflixService());
youTubeService = spy(new YouTubeService());
BusinessLookup businessLookup = spy(new BusinessLookup());
businessLookup.setEjbService(ejbService);
businessLookup.setJmsService(jmsService);
businessLookup.setNetflixService(netflixService);
businessLookup.setYouTubeService(youTubeService);
businessDelegate = spy(new BusinessDelegate());
businessDelegate.setLookupService(businessLookup);
}
/**
* In this example the client ({@link Client}) utilizes a business delegate (
* In this example the client ({@link MobileClient}) utilizes a business delegate (
* {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate
* service and makes the service call.
*/
@ -74,26 +69,20 @@ class BusinessDelegateTest {
void testBusinessDelegate() {
// setup a client object
var client = new Client(businessDelegate);
// set the service type
businessDelegate.setServiceType(ServiceType.EJB);
var client = new MobileClient(businessDelegate);
// action
client.doTask();
client.playbackMovie("Die hard");
// verifying that the businessDelegate was used by client during doTask() method.
verify(businessDelegate).doTask();
verify(ejbService).doProcessing();
// set the service type
businessDelegate.setServiceType(ServiceType.JMS);
// verifying that the businessDelegate was used by client during playbackMovie() method.
verify(businessDelegate).playbackMovie(anyString());
verify(netflixService).doProcessing();
// action
client.doTask();
client.playbackMovie("Maradona");
// verifying that the businessDelegate was used by client during doTask() method.
verify(businessDelegate, times(2)).doTask();
verify(jmsService).doProcessing();
verify(businessDelegate, times(2)).playbackMovie(anyString());
verify(youTubeService).doProcessing();
}
}