summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/rinha/backend/BackendApplication.java17
-rw-r--r--src/main/java/com/rinha/backend/controller/PaymentController.java24
-rw-r--r--src/main/java/com/rinha/backend/controller/SummaryController.java36
-rw-r--r--src/main/java/com/rinha/backend/model/PaymentModel.java52
-rw-r--r--src/main/java/com/rinha/backend/model/SummaryModel.java58
-rw-r--r--src/main/java/com/rinha/backend/repository/PaymentRepository.java10
-rw-r--r--src/main/java/com/rinha/backend/service/PaymentService.java116
-rw-r--r--src/main/java/com/rinha/backend/service/SummaryService.java41
-rw-r--r--src/main/resources/application.properties10
9 files changed, 364 insertions, 0 deletions
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<String> 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<SummaryModel> 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<PaymentModel, UUID> {
+}
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<PaymentModel> paymentQueue = new ArrayBlockingQueue<PaymentModel>(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<String> 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<PaymentModel> 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;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..86ad97b
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,10 @@
+spring.application.name=backend
+spring.datasource.url=jdbc:postgresql://postgres:5432/${DB}
+spring.datasource.username=${DB_USER}
+spring.datasource.password=${DB_PASSWORD}
+
+spring.threads.virtual.enabled=true
+
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.show-sql=false
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect