mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-21 18:26:35 +00:00
* added idempotent consumer pattern * updated doc * fixed bug in RequestService * add test converage * Add test converage to state machine * renamed module and added code example
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* The main entry point for the idempotent-consumer application.
|
||||
* This application demonstrates the use of the Idempotent Consumer
|
||||
* pattern which ensures that a message is processed exactly once
|
||||
* in scenarios where the same message can be delivered multiple times.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Idempotence">Idempotence (Wikipedia)</a>
|
||||
* @see <a href="https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html">Idempotent Consumer Pattern (Apache Camel)</a>
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@Slf4j
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
/**
|
||||
* The starting point of the CommandLineRunner
|
||||
* where the main program is run.
|
||||
*
|
||||
* @param requestService idempotent request service
|
||||
* @param requestRepository request jpa repository
|
||||
*/
|
||||
@Bean
|
||||
public CommandLineRunner run(RequestService requestService, RequestRepository requestRepository) {
|
||||
return args -> {
|
||||
Request req = requestService.create(UUID.randomUUID());
|
||||
requestService.create(req.getUuid());
|
||||
requestService.create(req.getUuid());
|
||||
LOGGER.info("Nb of requests : {}", requestRepository.count()); // 1, processRequest is idempotent
|
||||
req = requestService.start(req.getUuid());
|
||||
try {
|
||||
req = requestService.start(req.getUuid());
|
||||
} catch (InvalidNextStateException ex) {
|
||||
LOGGER.error("Cannot start request twice!");
|
||||
}
|
||||
req = requestService.complete(req.getUuid());
|
||||
LOGGER.info("Request: {}", req);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
/**
|
||||
* This exception is thrown when an invalid transition is attempted in the Statemachine
|
||||
* for the request status. This can occur when attempting to move to a state that is not valid
|
||||
* from the current state.
|
||||
*/
|
||||
public class InvalidNextStateException extends RuntimeException {
|
||||
public InvalidNextStateException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import java.util.UUID;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* The {@code Request} class represents a request with a unique UUID and a status.
|
||||
* The status of a request can be one of four values: PENDING, STARTED, COMPLETED, or INERROR.
|
||||
*/
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class Request {
|
||||
enum Status {
|
||||
PENDING,
|
||||
STARTED,
|
||||
COMPLETED
|
||||
}
|
||||
|
||||
@Id
|
||||
private UUID uuid;
|
||||
private Status status;
|
||||
|
||||
public Request(UUID uuid) {
|
||||
this(uuid, Status.PENDING);
|
||||
}
|
||||
|
||||
public Request(UUID uuid, Status status) {
|
||||
this.uuid = uuid;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class extends the RuntimeException class to handle scenarios where a Request is not found.
|
||||
* It is intended to be used where you would like to have a custom exception that signals that a requested object or action
|
||||
* was not found in the system, based on the UUID of the request.
|
||||
*
|
||||
*/
|
||||
public class RequestNotFoundException extends RuntimeException {
|
||||
RequestNotFoundException(UUID uuid) {
|
||||
super(String.format("Request %s not found", uuid));
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* This is a repository interface for the "Request" entity. It extends the JpaRepository interface from Spring Data JPA.
|
||||
* JpaRepository comes with many operations out of the box, including standard CRUD operations.
|
||||
* With JpaRepository, we are also able to leverage the power of Spring Data's query methods.
|
||||
* The UUID parameter in JpaRepository refers to the type of the ID in the "Request" entity.
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public interface RequestRepository extends JpaRepository<Request, UUID> {
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.idempotentconsumer;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* This service is responsible for handling request operations including
|
||||
* creation, start, and completion of requests.
|
||||
*/
|
||||
@Service
|
||||
public class RequestService {
|
||||
RequestRepository requestRepository;
|
||||
RequestStateMachine requestStateMachine;
|
||||
|
||||
public RequestService(RequestRepository requestRepository,
|
||||
RequestStateMachine requestStateMachine) {
|
||||
this.requestRepository = requestRepository;
|
||||
this.requestStateMachine = requestStateMachine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Request or returns an existing one by it's UUID.
|
||||
* This operation is idempotent: performing it once or several times
|
||||
* successively leads to an equivalent result.
|
||||
*
|
||||
* @param uuid The unique identifier for the Request.
|
||||
* @return Return existing Request or save and return a new Request.
|
||||
*/
|
||||
public Request create(UUID uuid) {
|
||||
Optional<Request> optReq = requestRepository.findById(uuid);
|
||||
if (!optReq.isEmpty()) {
|
||||
return optReq.get();
|
||||
}
|
||||
return requestRepository.save(new Request(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Request assigned with the given UUID.
|
||||
*
|
||||
* @param uuid The unique identifier for the Request.
|
||||
* @return The started Request.
|
||||
* @throws RequestNotFoundException if a Request with the given UUID is not found.
|
||||
*/
|
||||
public Request start(UUID uuid) {
|
||||
Optional<Request> optReq = requestRepository.findById(uuid);
|
||||
if (optReq.isEmpty()) {
|
||||
throw new RequestNotFoundException(uuid);
|
||||
}
|
||||
return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.STARTED));
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the Request assigned with the given UUID.
|
||||
*
|
||||
* @param uuid The unique identifier for the Request.
|
||||
* @return The completed Request.
|
||||
* @throws RequestNotFoundException if a Request with the given UUID is not found.
|
||||
*/
|
||||
public Request complete(UUID uuid) {
|
||||
Optional<Request> optReq = requestRepository.findById(uuid);
|
||||
if (optReq.isEmpty()) {
|
||||
throw new RequestNotFoundException(uuid);
|
||||
}
|
||||
return requestRepository.save(requestStateMachine.next(optReq.get(), Request.Status.COMPLETED));
|
||||
}
|
||||
}
|
||||
+63
@@ -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.idempotentconsumer;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* This class represents a state machine for managing request transitions.
|
||||
* It supports transitions to the statuses: PENDING, STARTED, and COMPLETED.
|
||||
*/
|
||||
@Component
|
||||
public class RequestStateMachine {
|
||||
|
||||
/**
|
||||
* Provides the next possible state of the request based on the current and next status.
|
||||
*
|
||||
* @param req The actual request object. This object MUST NOT be null and SHOULD have a valid status.
|
||||
* @param nextStatus Represents the next status that the request could transition to. MUST NOT be null.
|
||||
* @return A new Request object with updated status if the transition is valid.
|
||||
* @throws InvalidNextStateException If an invalid state transition is attempted.
|
||||
*/
|
||||
public Request next(Request req, Request.Status nextStatus) {
|
||||
String transitionStr = String.format("Transition: %s -> %s", req.getStatus(), nextStatus);
|
||||
switch (nextStatus) {
|
||||
case PENDING -> throw new InvalidNextStateException(transitionStr);
|
||||
case STARTED -> {
|
||||
if (Request.Status.PENDING.equals(req.getStatus())) {
|
||||
return new Request(req.getUuid(), Request.Status.STARTED);
|
||||
}
|
||||
throw new InvalidNextStateException(transitionStr);
|
||||
}
|
||||
case COMPLETED -> {
|
||||
if (Request.Status.STARTED.equals(req.getStatus())) {
|
||||
return new Request(req.getUuid(), Request.Status.COMPLETED);
|
||||
}
|
||||
throw new InvalidNextStateException(transitionStr);
|
||||
}
|
||||
default -> throw new InvalidNextStateException(transitionStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user