From 1a3a6f83cc9ef2af8fed16775dce209feb8391ab Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 27 Sep 2025 14:31:52 -0300 Subject: tentativa --- .../java/com/rinha/backend/BackendApplication.java | 17 +++ .../backend/controller/PaymentController.java | 24 +++++ .../backend/controller/SummaryController.java | 36 +++++++ .../java/com/rinha/backend/model/PaymentModel.java | 52 +++++++++ .../java/com/rinha/backend/model/SummaryModel.java | 58 +++++++++++ .../backend/repository/PaymentRepository.java | 10 ++ .../com/rinha/backend/service/PaymentService.java | 116 +++++++++++++++++++++ .../com/rinha/backend/service/SummaryService.java | 41 ++++++++ 8 files changed, 354 insertions(+) create mode 100644 src/main/java/com/rinha/backend/BackendApplication.java create mode 100644 src/main/java/com/rinha/backend/controller/PaymentController.java create mode 100644 src/main/java/com/rinha/backend/controller/SummaryController.java create mode 100644 src/main/java/com/rinha/backend/model/PaymentModel.java create mode 100644 src/main/java/com/rinha/backend/model/SummaryModel.java create mode 100644 src/main/java/com/rinha/backend/repository/PaymentRepository.java create mode 100644 src/main/java/com/rinha/backend/service/PaymentService.java create mode 100644 src/main/java/com/rinha/backend/service/SummaryService.java (limited to 'src/main/java/com/rinha/backend') diff --git a/src/main/java/com/rinha/backend/BackendApplication.java b/src/main/java/com/rinha/backend/BackendApplication.java new file mode 100644 index 0000000..1fbfe82 --- /dev/null +++ b/src/main/java/com/rinha/backend/BackendApplication.java @@ -0,0 +1,17 @@ +package com.rinha.backend; + +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.UUID; + +@SpringBootApplication +@RegisterReflectionForBinding(UUID[].class) +public class BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + +} diff --git a/src/main/java/com/rinha/backend/controller/PaymentController.java b/src/main/java/com/rinha/backend/controller/PaymentController.java new file mode 100644 index 0000000..bb0ce70 --- /dev/null +++ b/src/main/java/com/rinha/backend/controller/PaymentController.java @@ -0,0 +1,24 @@ +package com.rinha.backend.controller; + +import com.rinha.backend.model.PaymentModel; +import com.rinha.backend.service.PaymentService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/payments") +public class PaymentController { + + private final PaymentService paymentService; + + public PaymentController(PaymentService paymentService) { + this.paymentService = paymentService; + } + + @PostMapping + public ResponseEntity post(@RequestBody PaymentModel paymentModel){ + //System.out.println(paymentModel.getCorrelationId()); + paymentService.addQueue(paymentModel); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/rinha/backend/controller/SummaryController.java b/src/main/java/com/rinha/backend/controller/SummaryController.java new file mode 100644 index 0000000..73a6d7c --- /dev/null +++ b/src/main/java/com/rinha/backend/controller/SummaryController.java @@ -0,0 +1,36 @@ +package com.rinha.backend.controller; + +import com.rinha.backend.model.SummaryModel; +import com.rinha.backend.service.SummaryService; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.OffsetDateTime; + +@RestController +@RequestMapping("/payments-summary") +public class SummaryController { + + private final SummaryService summaryService; + + public SummaryController(SummaryService summaryService) { + this.summaryService = summaryService; + } + + @GetMapping + public ResponseEntity getSummary( + @RequestParam(value = "from", required = true) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + OffsetDateTime from, + @RequestParam(value = "to", required = true) + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + OffsetDateTime to + ) { + SummaryModel summary = summaryService.getSummary(from, to); + return ResponseEntity.ok(summary); + } +} diff --git a/src/main/java/com/rinha/backend/model/PaymentModel.java b/src/main/java/com/rinha/backend/model/PaymentModel.java new file mode 100644 index 0000000..992a834 --- /dev/null +++ b/src/main/java/com/rinha/backend/model/PaymentModel.java @@ -0,0 +1,52 @@ +package com.rinha.backend.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import java.time.OffsetDateTime; +import java.util.UUID; + +@Entity +@Table(name = "payments") +public class PaymentModel { + @Id + private UUID correlationId; + private float amount; + @JsonProperty("requestedAt") + private OffsetDateTime data; + private int processor; + + public int getProcessor() { + return processor; + } + + public void setProcessor(int processor) { + this.processor = processor; + } + + public UUID getCorrelationId() { + return correlationId; + } + + public void setCorrelationId(UUID correlationId) { + this.correlationId = correlationId; + } + + public float getAmount() { + return amount; + } + + public void setAmount(float amount) { + this.amount = amount; + } + + public OffsetDateTime getData() { + return data; + } + + public void setData(OffsetDateTime data) { + this.data = data; + } +} diff --git a/src/main/java/com/rinha/backend/model/SummaryModel.java b/src/main/java/com/rinha/backend/model/SummaryModel.java new file mode 100644 index 0000000..59c1097 --- /dev/null +++ b/src/main/java/com/rinha/backend/model/SummaryModel.java @@ -0,0 +1,58 @@ +package com.rinha.backend.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.Entity; + +public class SummaryModel { + @JsonProperty("default") + private ProcessorInfo processorDefault; + @JsonProperty("fallback") + private ProcessorInfo processorFallback; + + public SummaryModel() { + this.processorDefault = new ProcessorInfo(); + this.processorFallback = new ProcessorInfo(); + } + + public ProcessorInfo getProcessorDefault() { + return processorDefault; + } + + public ProcessorInfo getProcessorFallback() { + return processorFallback; + } + + public class ProcessorInfo { + private int totalRequests; + private double totalAmount; + + public ProcessorInfo() { + this.totalRequests = 0; + this.totalAmount = 0; + } + + public void addTotalRequests(int x){ + this.totalRequests += x; + } + + public void addTotalAmount(double x) { + this.totalAmount += x; + } + + public int getTotalRequests() { + return totalRequests; + } + + public void setTotalRequests(int totalRequests) { + this.totalRequests = totalRequests; + } + + public double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(double totalAmount) { + this.totalAmount = totalAmount; + } + } +} diff --git a/src/main/java/com/rinha/backend/repository/PaymentRepository.java b/src/main/java/com/rinha/backend/repository/PaymentRepository.java new file mode 100644 index 0000000..fa60e07 --- /dev/null +++ b/src/main/java/com/rinha/backend/repository/PaymentRepository.java @@ -0,0 +1,10 @@ +package com.rinha.backend.repository; + +import com.rinha.backend.model.PaymentModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; +@Repository +public interface PaymentRepository extends JpaRepository { +} diff --git a/src/main/java/com/rinha/backend/service/PaymentService.java b/src/main/java/com/rinha/backend/service/PaymentService.java new file mode 100644 index 0000000..bd283c3 --- /dev/null +++ b/src/main/java/com/rinha/backend/service/PaymentService.java @@ -0,0 +1,116 @@ +package com.rinha.backend.service; + +import com.rinha.backend.model.PaymentModel; +import com.rinha.backend.repository.PaymentRepository; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Random; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class PaymentService { + private static String processorDefault = "http://payment-processor-default:8080/payments"; + private static String processorFallback = "http://payment-processor-fallback:8080/payments"; + private final HttpClient httpClient = HttpClient.newHttpClient(); + private static final Random random = new Random(); + private final ExecutorService dbExecutor = Executors.newFixedThreadPool(2); + + private final BlockingQueue paymentQueue = new ArrayBlockingQueue(65536); + private final PaymentRepository paymentRepository; + + public PaymentService(PaymentRepository paymentRepository) { + this.paymentRepository = paymentRepository; + this.startWorker(); + } + + public void addQueue(PaymentModel p) { + p.setData(OffsetDateTime.now(ZoneOffset.UTC)); + paymentQueue.add(p); + } + + public void startWorker() { + for(int i = 1; i <= 4; ++i) { + Thread worker = new Thread(() -> { + while (true) { + try { + PaymentModel p = paymentQueue.take(); + processPayment(p); + } catch (InterruptedException e) { + } + } + }); + + worker.setName("worker-" + i); + worker.start(); + } + } + + public void processPayment(PaymentModel p) { + //System.out.println("Processando pagamento " + p.getCorrelationId() + + // " no valor de " + p.getAmount() + + // " às " + p.getData()); + + int ok = sendToProcessor(p); + if(ok > 0) { + p.setProcessor(ok); + saveDB(p); + }else + paymentQueue.add(p); + } + + public int sendToProcessor(PaymentModel p) { + try { + String body = String.format(""" + { + "correlationId": "%s", + "amount": %.2f, + "requestedAt": "%s" + } + """, p.getCorrelationId(), p.getAmount(), p.getData()); + + int n = random.nextInt(100); + String url; + if(n < 60) url = processorDefault; + else url = processorFallback; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + //System.out.println("Resposta servidor: " + response.statusCode() + " - " + response.body()); + if(response.statusCode() == 200 && url.equals(processorDefault)) + return 1; + if(response.statusCode() == 200 && url.equals(processorFallback)) + return 2; + return 0; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void saveDB(PaymentModel p){ + //paymentRepository.save(p); + dbExecutor.submit(() -> { + try { + paymentRepository.save(p); + } catch (Exception e) { + } + }); + } +} diff --git a/src/main/java/com/rinha/backend/service/SummaryService.java b/src/main/java/com/rinha/backend/service/SummaryService.java new file mode 100644 index 0000000..d3fb32e --- /dev/null +++ b/src/main/java/com/rinha/backend/service/SummaryService.java @@ -0,0 +1,41 @@ +package com.rinha.backend.service; + +import com.rinha.backend.model.PaymentModel; +import com.rinha.backend.model.SummaryModel; +import com.rinha.backend.repository.PaymentRepository; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.util.List; + +@Service +public class SummaryService { + + private final PaymentRepository paymentRepository; + + public SummaryService(PaymentRepository paymentRepository) { + this.paymentRepository = paymentRepository; + } + + public SummaryModel getSummary(OffsetDateTime from, OffsetDateTime to) { + SummaryModel summary = new SummaryModel(); + if (from == null) from = OffsetDateTime.MIN; + if (to == null) to = OffsetDateTime.MAX; + + List payments = paymentRepository.findAll(); + + for (PaymentModel p : payments) { + if(p.getData().isBefore(from) || p.getData().isAfter(to)) continue; + + if (p.getProcessor() == 1) { + summary.getProcessorDefault().addTotalRequests(1); + summary.getProcessorDefault().addTotalAmount(p.getAmount()); + } else if (p.getProcessor() == 2) { + summary.getProcessorFallback().addTotalRequests(1); + summary.getProcessorFallback().addTotalAmount(p.getAmount()); + } + } + + return summary; + } +} -- cgit v1.2.3