feat: Implement Actor Model pattern

* feat: Implement Actor Model pattern #3232

* feat: Implement Actor Model pattern #3232

* feat: update Actor Model implementation with multi-actor logic #3251

* feat: update Actor Model implementation with multi-actor logic and loose coupling  #3251

* test: add unit test for actor model #3251

* test: add test for App.java to increase coverage

* docs: add complete README for Actor Model pattern also implemented changes #3251
This commit is contained in:
ssrijan-007-sys
2025-04-22 23:59:58 +05:30
committed by GitHub
parent 1cde704bcb
commit fe522fd70d
12 changed files with 728 additions and 0 deletions
@@ -0,0 +1,63 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actormodel;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import lombok.Getter;
import lombok.Setter;
public abstract class Actor implements Runnable {
@Setter @Getter private String actorId;
private final BlockingQueue<Message> mailbox = new LinkedBlockingQueue<>();
private volatile boolean active =
true; // always read from main memory and written back to main memory,
// rather than being cached in a thread's local memory. To make it consistent to all Actors
public void send(Message message) {
mailbox.add(message); // Add message to queue
}
public void stop() {
active = false; // Stop the actor loop
}
@Override
public void run() {
while (active) {
try {
Message message = mailbox.take(); // Wait for a message
onReceive(message); // Process it
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Child classes must define what to do with a message
protected abstract void onReceive(Message message);
}
@@ -0,0 +1,51 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actormodel;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ActorSystem {
private final ExecutorService executor = Executors.newCachedThreadPool();
private final ConcurrentHashMap<String, Actor> actorRegister = new ConcurrentHashMap<>();
private final AtomicInteger idCounter = new AtomicInteger(0);
public void startActor(Actor actor) {
String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID
actor.setActorId(actorId); // assign the actor it's ID
actorRegister.put(actorId, actor); // Register and save the actor with it's ID
executor.submit(actor); // Run the actor in a thread
}
public Actor getActorById(String actorId) {
return actorRegister.get(actorId); // Find by Id
}
public void shutdown() {
executor.shutdownNow(); // Stop all threads
}
}
@@ -0,0 +1,64 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.
*/
/**
* The Actor Model is a design pattern used to handle concurrency in a safe, scalable, and
* message-driven way.
*
* <p>In the Actor Model: - An **Actor** is an independent unit that has its own state and behavior.
* - Actors **communicate only through messages** — they do not share memory. - An **ActorSystem**
* is responsible for creating, starting, and managing the lifecycle of actors. - Messages are
* delivered asynchronously, and each actor processes them one at a time.
*
* <p>💡 Key benefits: - No shared memory = no need for complex thread-safety - Easy to scale with
* many actors - Suitable for highly concurrent or distributed systems
*
* <p>🔍 This example demonstrates the Actor Model: - `ActorSystem` starts two actors: `srijan` and
* `ansh`. - `ExampleActor` and `ExampleActor2` extend the `Actor` class and override the
* `onReceive()` method to handle messages. - Actors communicate using `send()` to pass `Message`
* objects that include the message content and sender's ID. - The actors process messages
* **asynchronously in separate threads**, and we allow a short delay (`Thread.sleep`) to let them
* run. - The system is shut down gracefully at the end.
*/
package com.iluwatar.actormodel;
public class App {
public static void main(String[] args) throws InterruptedException {
ActorSystem system = new ActorSystem();
Actor srijan = new ExampleActor(system);
Actor ansh = new ExampleActor2(system);
system.startActor(srijan);
system.startActor(ansh);
ansh.send(new Message("Hello ansh", srijan.getActorId()));
srijan.send(new Message("Hello srijan!", ansh.getActorId()));
Thread.sleep(1000); // Give time for messages to process
srijan.stop(); // Stop the actor gracefully
ansh.stop();
system.shutdown(); // Stop the actor system
}
}
@@ -0,0 +1,53 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actormodel;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ExampleActor extends Actor {
private final ActorSystem actorSystem;
@Getter private final List<String> receivedMessages = new ArrayList<>();
public ExampleActor(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
}
// Logger log = Logger.getLogger(getClass().getName());
@Override
protected void onReceive(Message message) {
LOGGER.info(
"[{}]Received : {} from : [{}]", getActorId(), message.getContent(), message.getSenderId());
Actor sender = actorSystem.getActorById(message.getSenderId()); // sender actor id
// Reply of the message
if (sender != null && !message.getSenderId().equals(getActorId())) {
sender.send(new Message("I got your message ", getActorId()));
}
}
}
@@ -0,0 +1,46 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actormodel;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ExampleActor2 extends Actor {
private final ActorSystem actorSystem;
@Getter private final List<String> receivedMessages = new ArrayList<>();
public ExampleActor2(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
}
@Override
protected void onReceive(Message message) {
receivedMessages.add(message.getContent());
LOGGER.info("[{}]Received : {}", getActorId(), message.getContent());
}
}
@@ -0,0 +1,35 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actormodel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class Message {
private final String content;
private final String senderId;
}
@@ -0,0 +1,63 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 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.actor;
import static org.junit.jupiter.api.Assertions.*;
import com.iluwatar.actormodel.ActorSystem;
import com.iluwatar.actormodel.App;
import com.iluwatar.actormodel.ExampleActor;
import com.iluwatar.actormodel.ExampleActor2;
import com.iluwatar.actormodel.Message;
import org.junit.jupiter.api.Test;
public class ActorModelTest {
@Test
void testMainMethod() throws InterruptedException {
App.main(new String[] {});
}
@Test
public void testMessagePassing() throws InterruptedException {
ActorSystem system = new ActorSystem();
ExampleActor srijan = new ExampleActor(system);
ExampleActor2 ansh = new ExampleActor2(system);
system.startActor(srijan);
system.startActor(ansh);
// Ansh recieves a message from Srijan
ansh.send(new Message("Hello ansh", srijan.getActorId()));
// Wait briefly to allow async processing
Thread.sleep(200);
// Check that Srijan received the message
assertTrue(
ansh.getReceivedMessages().contains("Hello ansh"),
"ansh should receive the message from Srijan");
}
}