Ontwerp een REST API met het Command Pattern voor gestructureerde verzoekafhandeling
description
Met deze prompt leer ik hoe ik een REST API kan ontwerpen die met het Command Pattern verzoeken gestructureerd afhandelt, wat leidt tot betere onderhoudbaarheid en uitbreidbaarheid. Het helpt problemen met complexe endpointlogica te verminderen door verantwoordelijkheden te scheiden en biedt een heldere structuur voor foutafhandeling en logging.
prompt
Help me een REST API te ontwerpen die het Command Pattern toepast om verzoeken gestructureerd af te handelen. Mijn API-doel: <beschrijf het doel van mijn API>. Beschrijf hoe ik commando-objecten kan modelleren, hoe endpo ...
try_prompt
generate_helper
disclaimerOnPageApi
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Zeker! Hieronder vind je een overzicht en ontwerp voor een REST API die het Command Pattern toepast voor een webshop-bestelsysteem.
---
### 1. Conceptueel ontwerp van het Command Pattern in een REST API
**Doel:**
Gestructureerd en uitbreidbaar verzoekenafhandelingsmechanisme waarbij elk type actie in een apart commando-object wordt gemodelleerd.
**Werking:**
- Elke API-aanroep wordt vertaald naar een specifiek Command-object.
- Een centrale CommandHandler of Dispatcher voert het commando uit.
- Logging, foutafhandeling en eventueel transactional management worden rondom de uitvoering geregeld.
---
### 2. Modellering van commando-objecten
**Basisconcept:**
Maak voor elke bewerking een aparte Command-klasse, bijvoorbeeld:
```java
// Java-voorbeeld
public interface Command {
void execute();
}
public class PlaatsBestellingCommand implements Command {
private String klantId;
private List<Product> producten;
private String afleveradres;
// Constructor, getters, setters
public void execute() {
// Logica voor het plaatsen van een bestelling
}
}
```
**Voorbeelden van commando's:**
- `PlaatsBestellingCommand`
- `AnnuleerBestellingCommand`
- `UpdateBestellingStatusCommand`
- `VerzendBestellingCommand`
Elk commando bevat de benodigde data voor de actie en implementeert een `execute()`-methode.
---
### 3. REST API endpoints en aanroep van commando's
**Voorbeeld endpoints:**
| HTTP Verb | URL | Beschrijving | Comando-actie |
|------------|----------------------------|----------------------------------------|----------------------------------------|
| POST | `/bestellingen` | Nieuwe bestelling plaatsen | `PlaatsBestellingCommand` |
| POST | `/bestellingen/{id}/annuleren` | Bestelling annuleren | `AnnuleerBestellingCommand` |
| PUT | `/bestellingen/{id}/status` | Status bijwerken | `UpdateBestellingStatusCommand` |
**Implementatie:**
De REST-controller vertaalt inkomende JSON-gegevens naar command-objecten en stuurt deze door naar een handler.
```java
@PostMapping("/bestellingen")
public ResponseEntity<?> plaatsBestelling(@RequestBody PlaatsBestellingRequest request) {
PlaatsBestellingCommand command = new PlaatsBestellingCommand(
request.getKlantId(),
request.getProducten(),
request.getAfleveradres()
);
commandHandler.handle(command);
return ResponseEntity.ok().build();
}
```
---
### 4. Uitvoering, logging en foutafhandeling
**Uitvoering:**
- Een centrale `CommandHandler` of Dispatcher ontvangt het command.
- Het handler-antwoord kan een resultaat bevatten (bijvoorbeeld order-ID).
**Logging:**
- Log elke command-uitvoering met details (bezig met welke bestelling, gebruiker, timestamp).
- Gebruik een AOP-aspect of middleware om logging automatisch toe te passen.
**Foutafhandeling:**
- Tijdens `execute()` kunnen uitzonderingen optreden.
- Catch deze en zet ze om in passende HTTP-fouten (404, 400, 500).
**Voorbeeld:**
```java
public class CommandHandler {
public void handle(Command command) {
try {
// Log start
logger.info("Uitvoering commando: " + command.getClass().getSimpleName());
command.execute();
// Log success
logger.info("Succesvolle uitvoering");
} catch (Exception e) {
// Log fout
logger.error("Fout tijdens uitvoeren", e);
throw e; // of vertaal naar een geschikte HTTP-fout
}
}
}
```
---
### 5. Voordelen voor onderhoudbaarheid en uitbreidbaarheid
- **Modulariteit:**
Elke actie is geïsoleerd in een apart commando, waardoor wijzigingen beperkt blijven tot specifieke klassen.
- **Uitbreidbaarheid:**
Nieuwe acties toegevoegd door nieuwe command-klassen en endpoints te maken, zonder bestaande code te beïnvloeden.
- **Testbaarheid:**
Commando's kunnen los getest worden, inclusief logica en validatie.
- **Centralisatie van logica:**
De handler regelt logging, transacties en foutafhandeling op één plek.
---
### Samenvatting
Door het Command Pattern te integreren in je REST API ontwerp creëer je een gestructureerde manier om verzoeken te verwerken, die makkelijk te onderhouden en uit te breiden is. Elke API-aanroep vertaalt naar een specifiek command-object dat door een handler wordt uitgevoerd, terwijl logging en foutafhandeling centraal worden geregeld.
---
Wil je een volledige code-voorbeeld of verdere uitwerking van bepaalde onderdelen?
provider: deepseek
model: deepseek-chat
Zeker, ik help je graag een REST API te ontwerpen volgens het Command Pattern voor een webshop bestelsysteem. Hier is een gestructureerde aanpak:
## 1. Command Pattern Basisstructuur
### Basis Command Interface
```java
public interface OrderCommand {
void execute();
void undo();
CommandResult getResult();
String getCommandType();
}
```
### Command Result Object
```java
public class CommandResult {
private boolean success;
private String message;
private Object data;
private String errorCode;
// constructors, getters & setters
}
```
## 2. Concrete Command Implementaties
### Bestelling Aanmaken Command
```java
public class CreateOrderCommand implements OrderCommand {
private OrderService orderService;
private OrderRequest orderRequest;
private CommandResult result;
public CreateOrderCommand(OrderService orderService, OrderRequest orderRequest) {
this.orderService = orderService;
this.orderRequest = orderRequest;
}
@Override
public void execute() {
try {
Order order = orderService.createOrder(orderRequest);
this.result = new CommandResult(true, "Bestelling succesvol aangemaakt", order, null);
} catch (Exception e) {
this.result = new CommandResult(false, "Fout bij aanmaken bestelling", null, "ORDER_CREATE_ERROR");
}
}
@Override
public void undo() {
if (result.isSuccess()) {
orderService.cancelOrder(((Order)result.getData()).getId());
}
}
@Override
public CommandResult getResult() { return result; }
@Override
public String getCommandType() { return "CREATE_ORDER"; }
}
```
### Bestelling Annuleren Command
```java
public class CancelOrderCommand implements OrderCommand {
private OrderService orderService;
private String orderId;
private CommandResult result;
@Override
public void execute() {
try {
Order cancelledOrder = orderService.cancelOrder(orderId);
this.result = new CommandResult(true, "Bestelling geannuleerd", cancelledOrder, null);
} catch (Exception e) {
this.result = new CommandResult(false, "Fout bij annuleren bestelling", null, "ORDER_CANCEL_ERROR");
}
}
@Override
public void undo() {
// Herstel de geannuleerde bestelling
orderService.restoreOrder(orderId);
}
// overige methoden...
}
```
### Betaling Verwerken Command
```java
public class ProcessPaymentCommand implements OrderCommand {
private PaymentService paymentService;
private PaymentRequest paymentRequest;
private CommandResult result;
@Override
public void execute() {
try {
Payment payment = paymentService.processPayment(paymentRequest);
this.result = new CommandResult(true, "Betaling verwerkt", payment, null);
} catch (PaymentException e) {
this.result = new CommandResult(false, "Betaling mislukt", null, "PAYMENT_FAILED");
}
}
// overige methoden...
}
```
## 3. Command Invoker met Logging en Foutafhandeling
```java
@Service
public class CommandInvoker {
private static final Logger logger = LoggerFactory.getLogger(CommandInvoker.class);
private final List<OrderCommand> commandHistory = new ArrayList<>();
public CommandResult executeCommand(OrderCommand command) {
try {
logger.info("Uitvoeren commando: {}", command.getCommandType());
command.execute();
CommandResult result = command.getResult();
if (result.isSuccess()) {
commandHistory.add(command);
logger.info("Commando succesvol uitgevoerd: {}", command.getCommandType());
} else {
logger.error("Commando mislukt: {} - {}", command.getCommandType(), result.getMessage());
}
return result;
} catch (Exception e) {
logger.error("Onverwachte fout bij uitvoeren commando: {}", command.getCommandType(), e);
return new CommandResult(false, "Interne serverfout", null, "INTERNAL_ERROR");
}
}
public void undoLastCommand() {
if (!commandHistory.isEmpty()) {
OrderCommand lastCommand = commandHistory.remove(commandHistory.size() - 1);
lastCommand.undo();
logger.info("Commando ongedaan gemaakt: {}", lastCommand.getCommandType());
}
}
}
```
## 4. REST Endpoints
### Order Controller
```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private CommandInvoker commandInvoker;
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<CommandResult> createOrder(@RequestBody OrderRequest orderRequest) {
CreateOrderCommand command = new CreateOrderCommand(orderService, orderRequest);
CommandResult result = commandInvoker.executeCommand(command);
return ResponseEntity.status(result.isSuccess() ? 201 : 400).body(result);
}
@PostMapping("/{orderId}/cancel")
public ResponseEntity<CommandResult> cancelOrder(@PathVariable String orderId) {
CancelOrderCommand command = new CancelOrderCommand(orderService, orderId);
CommandResult result = commandInvoker.executeCommand(command);
return ResponseEntity.ok(result);
}
@PostMapping("/{orderId}/payment")
public ResponseEntity<CommandResult> processPayment(@PathVariable String orderId,
@RequestBody PaymentRequest paymentRequest) {
ProcessPaymentCommand command = new ProcessPaymentCommand(paymentService, paymentRequest);
CommandResult result = commandInvoker.executeCommand(command);
return ResponseEntity.ok(result);
}
@PostMapping("/undo")
public ResponseEntity<String> undoLastCommand() {
commandInvoker.undoLastCommand();
return ResponseEntity.ok("Laatste commando ongedaan gemaakt");
}
}
```
## 5. Uitbreiding: Command Factory
```java
@Component
public class CommandFactory {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
public OrderCommand createCommand(String commandType, Object request) {
switch (commandType) {
case "CREATE_ORDER":
return new CreateOrderCommand(orderService, (OrderRequest) request);
case "CANCEL_ORDER":
return new CancelOrderCommand(orderService, (String) request);
case "PROCESS_PAYMENT":
return new ProcessPaymentCommand(paymentService, (PaymentRequest) request);
default:
throw new IllegalArgumentException("Onbekend commando type: " + commandType);
}
}
}
```
## 6. Geavanceerde Features
### Asynchrone Command Verwerking
```java
@Async
public CompletableFuture<CommandResult> executeCommandAsync(OrderCommand command) {
return CompletableFuture.supplyAsync(() -> executeCommand(command));
}
```
### Command Queue voor Batch Verwerking
```java
@Component
public class CommandQueue {
private final Queue<OrderCommand> pendingCommands = new ConcurrentLinkedQueue<>();
public void addCommand(OrderCommand command) {
pendingCommands.add(command);
}
@Scheduled(fixedRate = 5000)
public void processQueue() {
while (!pendingCommands.isEmpty()) {
OrderCommand command = pendingCommands.poll();
commandInvoker.executeCommand(command);
}
}
}
```
## 7. Voordelen voor Onderhoudbaarheid en Uitbreidbaarheid
### 🎯 **Onderhoudbaarheid**
- **Separation of Concerns**: Elk commando encapsuleert specifieke business logic
- **Eenvoudig testbaar**: Commando's kunnen geïsoleerd worden getest
- **Centrale logging**: Alle logging gebeurt op één plek (CommandInvoker)
- **Consistente foutafhandeling**: Gestandaardiseerde error handling
### 🔧 **Uitbreidbaarheid**
- **Nieuwe features**: Voeg simpelweg nieuwe command klassen toe
- **Wijzigingen**: Pas individuele commando's aan zonder andere te beïnvloeden
- **Complexe workflows**: Combineer meerdere commando's voor geavanceerde processen
- **Undo/Redo**: Eenvoudige implementatie van geschiedenis functionaliteit
### 📊 **Monitoring en Debugging**
```java
// Voorbeeld: Command metrics tracking
@Component
public class CommandMetrics {
private final MeterRegistry meterRegistry;
private final Map<String, Counter> successCounters = new ConcurrentHashMap<>();
private final Map<String, Counter> errorCounters = new ConcurrentHashMap<>();
public void recordCommandExecution(String commandType, boolean success) {
String counterName = success ? "command.success" : "command.error";
Map<String, Counter> counters = success ? successCounters : errorCounters;
counters.computeIfAbsent(commandType,
type -> meterRegistry.counter(counterName, "command_type", type))
.increment();
}
}
```
## 8. Gebruik in Practice
### Voorbeeld API Aanroep:
```bash
# Bestelling aanmaken
POST /api/orders
{
"customerId": "123",
"items": [
{"productId": "P001", "quantity": 2},
{"productId": "P002", "quantity": 1}
],
"shippingAddress": "Straat 123, Amsterdam"
}
# Betaling verwerken
POST /api/orders/ORD-123/payment
{
"paymentMethod": "CREDIT_CARD",
"amount": 99.50,
"cardToken": "tok_123456"
}
```
Dit design biedt een robuuste, schaalbare en onderhoudbare foundation voor je bestelsysteem API. Het Command Pattern zorgt voor een duidelijke scheiding van verantwoordelijkheden en maakt toekomstige uitbreidingen eenvoudig.