diff options
Diffstat (limited to 'src/main/java/com')
8 files changed, 354 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; +    } +} | 
