From d87e8cf10d6ba166d514cc7790d74fe203741d2b Mon Sep 17 00:00:00 2001 From: karthikbhat13 Date: Sun, 8 Aug 2021 13:51:27 +0100 Subject: [PATCH] feature: Added FanOut/FanIn Pattern (#1800) * Added FanOut/FanIn Pattern (#8) * #1627 adding fanout-fanin pattern * #1627 adding class diagram image * #1627 adding readme * #1627 adding license * #1627 updating relations * #1627 interrupting the thread * #1627 fixing sonar issues * #1627 fixing sonar issues * #1627 adding more info in README.md * Added FanOut/FanIn (#9) * #1627 adding fanout-fanin pattern * #1627 adding class diagram image * #1627 adding readme * #1627 adding license * #1627 updating relations * #1627 interrupting the thread * #1627 fixing sonar issues * #1627 fixing sonar issues * #1627 adding more info in README.md * #1627 adding programmatic examples in README.md --- fanout-fanin/README.md | 120 ++++++++++++++++++ fanout-fanin/etc/fanout-fanin.png | Bin 0 -> 39723 bytes fanout-fanin/etc/fanout-fanin.urm.puml | 39 ++++++ fanout-fanin/pom.xml | 69 ++++++++++ .../java/com/iluwatar/fanout/fanin/App.java | 74 +++++++++++ .../com/iluwatar/fanout/fanin/Consumer.java | 48 +++++++ .../iluwatar/fanout/fanin/FanOutFanIn.java | 62 +++++++++ .../fanout/fanin/SquareNumberRequest.java | 63 +++++++++ .../com/iluwatar/fanout/fanin/AppTest.java | 36 ++++++ .../fanout/fanin/FanOutFanInTest.java | 47 +++++++ .../fanout/fanin/SquareNumberRequestTest.java | 41 ++++++ gpl-3.0.txt | 23 ++++ lgpl-3.0.txt | 23 ++++ pom.xml | 1 + 14 files changed, 646 insertions(+) create mode 100644 fanout-fanin/README.md create mode 100644 fanout-fanin/etc/fanout-fanin.png create mode 100644 fanout-fanin/etc/fanout-fanin.urm.puml create mode 100644 fanout-fanin/pom.xml create mode 100644 fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java create mode 100644 fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java create mode 100644 fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java create mode 100644 fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java create mode 100644 fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java create mode 100644 fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java create mode 100644 fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/SquareNumberRequestTest.java diff --git a/fanout-fanin/README.md b/fanout-fanin/README.md new file mode 100644 index 000000000..5f9cc8df5 --- /dev/null +++ b/fanout-fanin/README.md @@ -0,0 +1,120 @@ +--- +layout: pattern +title: Fan-Out/Fan-In +folder: fanout-fanin +permalink: /patterns/fanout-fanin/ +categories: Integration +language: en +tags: +- Microservices +--- + +## Intent +The pattern is used when a source system needs to run one or more long-running processes that will fetch some data. +The source will not block itself waiting for the reply.
The pattern will run the same function in multiple +services or machines to fetch the data. This is equivalent to invoking the function multiple times on different chunks of data. + +## Explanation +The FanOut/FanIn service will take in a list of requests and a consumer. Each request might complete at a different time. +FanOut/FanIn service will accept the input params and returns the initial system an ID to acknowledge that the pattern +service has received the requests. Now the caller will not wait or expect the result in the same connection. + +Meanwhile, the pattern service will invoke the requests that have come. The requests might complete at different time. +These requests will be processed in different instances of the same function in different machines or services. As the +requests get completed, a callback service everytime is called that transforms the result into a common single object format +that gets pushed to a consumer. The caller will be at the other end of the consumer receiving the result. + +**Programmatic Example** + +The implementation provided has a list of numbers and end goal is to square the numbers and add them to a single result. +`FanOutFanIn` class receives the list of numbers in the form of list of `SquareNumberRequest` and a `Consumer` instance +that collects the results as the requests get over. `SquareNumberRequest` will square the number with a random delay +to give the impression of a long-running process that can complete at any time. `Consumer` instance will add the results from +different `SquareNumberRequest` that will come random time instances. + +Let's look at `FanOutFanIn` class that fans out the requests in async processes. + +```java +public class FanOutFanIn { + public static Long fanOutFanIn( + final List requests, final Consumer consumer) { + + ExecutorService service = Executors.newFixedThreadPool(requests.size()); + + // fanning out + List> futures = + requests.stream() + .map( + request -> + CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service)) + .collect(Collectors.toList()); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + + return consumer.getSumOfSquaredNumbers().get(); + } +} +``` + +`Consumer` is used a callback class that will be called when a request is completed. This will aggregate +the result from all requests. + +```java +public class Consumer { + + private final AtomicLong sumOfSquaredNumbers; + + Consumer(Long init) { + sumOfSquaredNumbers = new AtomicLong(init); + } + + public Long add(final Long num) { + return sumOfSquaredNumbers.addAndGet(num); + } +} +``` + +Request is represented as a `SquareNumberRequest` that squares the number with random delay and calls the +`Consumer` once it is squared. + +```java +public class SquareNumberRequest { + + private final Long number; + public void delayedSquaring(final Consumer consumer) { + + var minTimeOut = 5000L; + + SecureRandom secureRandom = new SecureRandom(); + var randomTimeOut = secureRandom.nextInt(2000); + + try { + // this will make the thread sleep from 5-7s. + Thread.sleep(minTimeOut + randomTimeOut); + } catch (InterruptedException e) { + LOGGER.error("Exception while sleep ", e); + Thread.currentThread().interrupt(); + } finally { + consumer.add(number * number); + } + } +} +``` + +## Class diagram +![alt-text](./etc/fanout-fanin.png) + +## Applicability + +Use this pattern when you can divide the workload into multiple chunks that can be dealt with separately. + +## Related patterns + +* [Aggregator Microservices](https://java-design-patterns.com/patterns/aggregator-microservices/) +* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/) + +## Credits + +* [Understanding Azure Durable Functions - Part 8: The Fan Out/Fan In Pattern](http://dontcodetired.com/blog/post/Understanding-Azure-Durable-Functions-Part-8-The-Fan-OutFan-In-Pattern) +* [Fan-out/fan-in scenario in Durable Functions - Cloud backup example](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-cloud-backup) +* [Understanding the Fan-Out/Fan-In API Integration Pattern](https://dzone.com/articles/understanding-the-fan-out-fan-in-api-integration-p) \ No newline at end of file diff --git a/fanout-fanin/etc/fanout-fanin.png b/fanout-fanin/etc/fanout-fanin.png new file mode 100644 index 0000000000000000000000000000000000000000..e4b85006cf1c9c200877917a082495712257fcf9 GIT binary patch literal 39723 zcmb4rby$?!*ESe{Afc3kgd!lVFmwq>mvnG9s&`+`~r2?g48KJ=P6O9g@hPr;1ndBYNtC0!A z_NraOT771m(ZkMa@hT%1qp_tb{mLEg zr#;h4f8R}kMwdb9>r66Q@)rB2STQXdL8|2Oz=^ImW~qu6>Yr2#d~lzpe${fjEAr;+ zXF|+nrgtxnZuYdLe{1$qAwxm&LXmtWr0k@-o`~*5AWHFbg*v68Ph?ZaWiL%)Q{~o| zoEw-=BwnU}hzLVbe1(_&xEB9;#7i#}Z@gEcK5y{u-V)xFY}-ByZ*+)7Oit+__a{zV zr#~6tX6nr#{c)@Ri&vL)~s*?}s zxKB`u_!Jct4?BRn&@_)sC06%aps16-ef!qU@HPrc_}h47oy*>81-cwcOn8$oRw#vF zQehqn${SXTLBvXUC`yA{&BghNoSdAgsj1s76qFdWN?sJ2*2&5EoP|I_h7fXo5>ir9 z3W|&6eky@`C@2qbE8*F1>dQTj7NTLfBXTk_GHWCGI}4qyAwi+#5})C{E;@a*@=5c( z$@OZ5x{ZyE23z%K`&x>M_meSDP(F*Ro^VMw+fLPrqM45t80NafdKW*}NpV;mN=dL= z>Oqv6D)Gn&XzJMW$^1sLLEYIhB)5wn0;r=x7 z`kt8XWGZnS&Aq+7jA5Xj!*ZWD4!v4|w)4(nzDn*$s%Vg+l2VIv;M=)si#;&ekOwb( zLf%i;I5TQ@O;%;@XjWLXYplo+F>6Hw@*gG-ojG|^S&h6>xS?Gvl(11PF;&=X&Udf0`l)Eko z%N;Gy(a&XEVm0jOv)x-AHsyvuBBD%bV_)35bt`Oqa1LTQ5#bnMx$x^((;tie)Z17o zkH$vZ7`2@l%HwF|$*bc{$BG_Tkzo;2#g{OT4`gSu(Ir&3wMh`=cB+K&s}+{5eb0C+Y;}wx9EN`7HI^Oo(Pw0%hxOZn1Q}g9^Q_?3! zBA1sXynp`;#z$v;^ytwKg?Z*Yy(F-U);!_s@&ZTm4t~7>YV0J~T8RSg)z9iL-V{&p zNjf|1F87yC6zbJmPL}sKb;Yu+grm8Ezq_NhrBS7$!os%t#VNj!f`tlNcsbt{8{Q&S zV))FuMRgn(6;r})gj#Dql({LEKrWG4O=z;}EA|)BI7Wt(F4d~qfvA}GU_QWQaLM@| z==I!0k-?rq*yzova!25o44V#S_W$|a8d^2BFDL$1dky=w_tTdVBa*v!-X*!U}uKX!> zgx_)`Q$>y&AuGQc%%4t{2D`oEazZZ;q6q}v{lK^~5gjm5;YH%TrTO_EMn4?ZMr1z) z0{l}Up(f=Z?kpNUJai(7PzoqBAH~7M{PixNSicqa@(+q8qSlpDE0NVOFl=!kLmI4J zFF?eVfsCc@cuva+NR*hw%L2(B+SBiOovIeGCDf+hAg>B7GU6GuePwzh*WXHoONzi? zy)OBI*;*CM8%$R&N8tJ%io*C2KxH4(VztBC)&d_&gM9-6^>t)S%!8a%{nn6{D`Z85 zqQA5R#u)-Yex(K(h+9KOt3%lU^3KjrHn%TFzPcP454=7n1D{sz$K^=;tC0f(0|^NU zniW>H04=?|yuN?`{_*3j%fMrRs%3W&7KUdFg+dQ7P#9x$Yh4yPqpc@OO?MW%?d
&&lkhN3}0Jg+Yir(Om^GTFN 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 yS26 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, consumer : Consumer) : Long {static} + } + class SquareNumberRequest { + - LOGGER : Logger {static} + - number : Long + + SquareNumberRequest(number : Long) + + delayedSquaring(consumer : Consumer) + } + + object SquareNumberRequest1 + object SquareNumberRequest2 + object SquareNumberRequest3 + diamond dia +} + +App --> FanOutFanIn +FanOutFanIn --> "fan out - running in parallel" SquareNumberRequest1 +FanOutFanIn --> "fan out" SquareNumberRequest2 +FanOutFanIn --> "fan out" SquareNumberRequest3 +SquareNumberRequest1 --> "fan in - aggregate using callback" dia +SquareNumberRequest2 --> "fan in" dia +SquareNumberRequest3 --> "fan in" dia +dia --> Consumer +@enduml \ No newline at end of file diff --git a/fanout-fanin/pom.xml b/fanout-fanin/pom.xml new file mode 100644 index 000000000..382cc342e --- /dev/null +++ b/fanout-fanin/pom.xml @@ -0,0 +1,69 @@ + + + + + java-design-patterns + com.iluwatar + 1.25.0-SNAPSHOT + + 4.0.0 + + fanout-fanin + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.fanout.fanin.App + + + + + + + + + + \ No newline at end of file diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java new file mode 100644 index 000000000..cc70ff141 --- /dev/null +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java @@ -0,0 +1,74 @@ +/* + * 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.fanout.fanin; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + + + +/** + * FanOut/FanIn pattern is a concurrency pattern that refers to executing multiple instances of the + * activity function concurrently. The "fan out" part is essentially splitting the data into + * multiple chunks and then calling the activity function multiple times, passing the chunks. + * + *

When each chunk has been processed, the "fan in" takes place that aggregates results from each + * instance of function and forms a single final result. + * + *

This pattern is only really useful if you can “chunk” the workload in a meaningful way for + * splitting up to be processed in parallel. + */ +@Slf4j +public class App { + + /** + * Entry point. + * + *

Implementation provided has a list of numbers that has to be squared and added. The list can + * be chunked in any way and the "activity function" {@link + * SquareNumberRequest#delayedSquaring(Consumer)} i.e. squaring the number ca be done + * concurrently. The "fan in" part is handled by the {@link Consumer} that takes in the result + * from each instance of activity and aggregates it whenever that particular activity function + * gets over. + */ + public static void main(String[] args) { + final List numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L); + + LOGGER.info("Numbers to be squared and get sum --> {}", numbers); + + final List requests = + numbers.stream().map(SquareNumberRequest::new).collect(Collectors.toList()); + + var consumer = new Consumer(0L); + + // Pass the request and the consumer to fanOutFanIn or sometimes referred as Orchestrator + // function + final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer); + + LOGGER.info("Sum of all squared numbers --> {}", sumOfSquaredNumbers); + } +} diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java new file mode 100644 index 000000000..79e3445dc --- /dev/null +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java @@ -0,0 +1,48 @@ +/* + * 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.fanout.fanin; + +import java.util.concurrent.atomic.AtomicLong; + +import lombok.Getter; + + + +/** + * Consumer or callback class that will be called everytime a request is complete This will + * aggregate individual result to form a final result. + */ +@Getter +public class Consumer { + + private final AtomicLong sumOfSquaredNumbers; + + Consumer(Long init) { + sumOfSquaredNumbers = new AtomicLong(init); + } + + public Long add(final Long num) { + return sumOfSquaredNumbers.addAndGet(num); + } +} diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java new file mode 100644 index 000000000..71af5ae9f --- /dev/null +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java @@ -0,0 +1,62 @@ +/* + * 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.fanout.fanin; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +/** + * FanOutFanIn class processes long running requests, when any of the processes gets over, result is + * passed over to the consumer or the callback function. Consumer will aggregate the results as they + * keep on completing. + */ +public class FanOutFanIn { + + /** + * the main fanOutFanIn function or orchestrator function. + * @param requests List of numbers that need to be squared and summed up + * @param consumer Takes in the squared number from {@link SquareNumberRequest} and sums it up + * @return Aggregated sum of all squared numbers. + */ + public static Long fanOutFanIn( + final List requests, final Consumer consumer) { + + ExecutorService service = Executors.newFixedThreadPool(requests.size()); + + // fanning out + List> futures = + requests.stream() + .map( + request -> + CompletableFuture.runAsync(() -> request.delayedSquaring(consumer), service)) + .collect(Collectors.toList()); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + + return consumer.getSumOfSquaredNumbers().get(); + } +} diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java new file mode 100644 index 000000000..73db6ca87 --- /dev/null +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java @@ -0,0 +1,63 @@ +/* + * 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.fanout.fanin; + +import java.security.SecureRandom; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * Squares the number with a little timeout to give impression of long running process that return + * at different times. + */ +@Slf4j +@AllArgsConstructor +public class SquareNumberRequest { + + private final Long number; + + /** + * Squares the number with a little timeout to give impression of long running process that return + * at different times. + * @param consumer callback class that takes the result after the delay. + * */ + public void delayedSquaring(final Consumer consumer) { + + var minTimeOut = 5000L; + + SecureRandom secureRandom = new SecureRandom(); + var randomTimeOut = secureRandom.nextInt(2000); + + try { + // this will make the thread sleep from 5-7s. + Thread.sleep(minTimeOut + randomTimeOut); + } catch (InterruptedException e) { + LOGGER.error("Exception while sleep ", e); + Thread.currentThread().interrupt(); + } finally { + consumer.add(number * number); + } + } +} diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java new file mode 100644 index 000000000..d221066e2 --- /dev/null +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java @@ -0,0 +1,36 @@ +/* + * 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.fanout.fanin; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AppTest { + + @Test + void shouldLaunchApp() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } +} diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java new file mode 100644 index 000000000..4273d756a --- /dev/null +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java @@ -0,0 +1,47 @@ +/* + * 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.fanout.fanin; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +class FanOutFanInTest { + + @Test + void fanOutFanInTest() { + final List numbers = Arrays.asList(1L, 3L, 4L, 7L, 8L); + + final List requests = + numbers.stream().map(SquareNumberRequest::new).collect(Collectors.toList()); + + final Consumer consumer = new Consumer(0L); + + final Long sumOfSquaredNumbers = FanOutFanIn.fanOutFanIn(requests, consumer); + + Assertions.assertEquals(139, sumOfSquaredNumbers); + } +} diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/SquareNumberRequestTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/SquareNumberRequestTest.java new file mode 100644 index 000000000..86560b38b --- /dev/null +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/SquareNumberRequestTest.java @@ -0,0 +1,41 @@ +/* + * 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.fanout.fanin; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SquareNumberRequestTest { + + @Test + void delayedSquaringTest() { + Consumer consumer = new Consumer(10L); + + SquareNumberRequest squareNumberRequest = new SquareNumberRequest(5L); + + squareNumberRequest.delayedSquaring(consumer); + + Assertions.assertEquals(35, consumer.getSumOfSquaredNumbers().get()); + } +} diff --git a/gpl-3.0.txt b/gpl-3.0.txt index e72bfddab..6dc84f60c 100644 --- a/gpl-3.0.txt +++ b/gpl-3.0.txt @@ -1,3 +1,26 @@ +==== + 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. +==== + GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/lgpl-3.0.txt b/lgpl-3.0.txt index 0a041280b..d1fcc084e 100644 --- a/lgpl-3.0.txt +++ b/lgpl-3.0.txt @@ -1,3 +1,26 @@ +==== + 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. +==== + GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/pom.xml b/pom.xml index 1ebe0ad23..cfd0de127 100644 --- a/pom.xml +++ b/pom.xml @@ -232,6 +232,7 @@ table-module presentation lockable-object + fanout-fanin domain-model