> deletePayment(String paymentId);
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/handler/impl/PaymentHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.handler.impl;
2 |
3 | import com.politrons.application.handler.PaymentHandler;
4 | import com.politrons.application.model.command.AddPaymentCommand;
5 | import com.politrons.application.model.command.PaymentCommand;
6 | import com.politrons.application.model.command.UpdatePaymentCommand;
7 | import com.politrons.application.model.error.ErrorPayload;
8 | import com.politrons.domain.PaymentStateAggregateRoot;
9 | import com.politrons.domain.entities.BeneficiaryParty;
10 | import com.politrons.domain.entities.DebtorParty;
11 | import com.politrons.domain.entities.PaymentInfo;
12 | import com.politrons.domain.entities.SponsorParty;
13 | import com.politrons.infrastructure.repository.PaymentRepository;
14 | import io.vavr.API;
15 | import io.vavr.Function1;
16 | import io.vavr.concurrent.Future;
17 | import io.vavr.control.Either;
18 | import lombok.AllArgsConstructor;
19 | import lombok.NoArgsConstructor;
20 |
21 | import javax.enterprise.context.ApplicationScoped;
22 | import javax.inject.Inject;
23 |
24 | import static io.vavr.API.*;
25 | import static io.vavr.Patterns.$Left;
26 | import static io.vavr.Patterns.$Right;
27 |
28 | @NoArgsConstructor
29 | @AllArgsConstructor
30 | @ApplicationScoped
31 | public class PaymentHandlerImpl implements PaymentHandler {
32 |
33 | @Inject
34 | PaymentRepository paymentRepository;
35 |
36 |
37 | /**
38 | * Handler to receive a Command to add a payment and return the id of the operation.
39 | *
40 | * Since the operation to database it take time and it might not be part of the machine we
41 | * need to make this operation async. As personal level I like Vavr library for functional programing in Java.(Pretty much like Scala monads)
42 | *
43 | * Transform the [AddPaymentCommand] in a new [PaymentStateAggregateRoot] with [type] state as "created"
44 | *
45 | * Also since we have to deal with impure world, we need to control effects. So in order to control the possibility
46 | * of a Database problem, or network, we will catch the throwable and we will transform into Either of possible error [Left]
47 | * or the expected output [Right]
48 | *
49 | * @param addPaymentCommand to be transformed into Domain model to be persisted.
50 | * @return the id of the event to be modified or deleted in the future
51 | */
52 | @Override
53 | public Future> addPayment(AddPaymentCommand addPaymentCommand) {
54 | return paymentRepository.addPayment(getPaymentAggregateRoot(addPaymentCommand, PaymentStateAggregateRoot::create))
55 | .map(this::processRepositoryResponse);
56 | }
57 |
58 | /**
59 | * Handler to transform the [UpdatePaymentCommand] in a new [PaymentStateAggregateRoot] with [type] state as "changed"
60 | *
61 | * @param updatePaymentCommand command to be transformed into Domain model with state "changed"
62 | * @return a new id of the row of the new Event with the new state.
63 | */
64 | @Override
65 | public Future> updatePayment(UpdatePaymentCommand updatePaymentCommand) {
66 | return paymentRepository.updatePayment(getPaymentAggregateRoot(updatePaymentCommand, PaymentStateAggregateRoot::update))
67 | .map(this::processRepositoryResponse);
68 | }
69 |
70 | /**
71 | * Handler to get the the previous event created using the id, then we change the state as [deleted] and
72 | * finally we delegate the creation of the new event into the infrastructure layer."
73 | *
74 | * @param eventId from the previous event to fetch and change state.
75 | * @return a new id of the row of the new Event with the new state.
76 | */
77 | @Override
78 | public Future> deletePayment(String eventId) {
79 | return paymentRepository.fetchPayment(eventId)
80 | .recover(API::Left)
81 | .flatMap(either -> Match(either).of(
82 | Case($Right($()), paymentStateAggregateRoot ->
83 | paymentRepository.deletePayment(PaymentStateAggregateRoot.delete(paymentStateAggregateRoot))
84 | .map(this::processRepositoryResponse)),
85 | Case($Left($()), t -> Future.of(() -> Left(new ErrorPayload(500, t.getMessage()))))
86 | ));
87 | }
88 |
89 | private Either processRepositoryResponse(Either either) {
90 | return Match(either).of(
91 | Case($Right($()), API::Right),
92 | Case($Left($()), t -> Left(new ErrorPayload(500, t.getMessage()))));
93 | }
94 |
95 | //#############################//
96 | // DOMAIN FACTORY //
97 | //#############################//
98 |
99 | private PaymentStateAggregateRoot getPaymentAggregateRoot(PaymentCommand paymentCommand,
100 | Function1 changeStateFunc) {
101 | PaymentInfo paymentInfo = getPaymentInfo(paymentCommand);
102 | return changeStateFunc.apply(paymentInfo);
103 | }
104 |
105 | private PaymentInfo getPaymentInfo(PaymentCommand paymentCommand) {
106 | return PaymentInfo.create(paymentCommand.getAmount(),
107 | paymentCommand.getCurrency(),
108 | paymentCommand.getPaymentId(),
109 | paymentCommand.getPaymentPurpose(),
110 | paymentCommand.getPaymentType(),
111 | paymentCommand.getProcessingDate(),
112 | paymentCommand.getReference(),
113 | paymentCommand.getSchemePaymentSubType(),
114 | paymentCommand.getSchemePaymentType(),
115 | getDebtorParty(paymentCommand),
116 | getSponsorParty(paymentCommand),
117 | getBeneficiaryParty(paymentCommand));
118 | }
119 |
120 | private SponsorParty getSponsorParty(PaymentCommand paymentCommand) {
121 | return SponsorParty.create(paymentCommand.getSponsorParty().getAccountNumber(),
122 | paymentCommand.getSponsorParty().getBankId(),
123 | paymentCommand.getSponsorParty().getBankIdCode());
124 | }
125 |
126 | private DebtorParty getDebtorParty(PaymentCommand paymentCommand) {
127 | return DebtorParty.create(paymentCommand.getDebtorParty().getAccountName(),
128 | paymentCommand.getDebtorParty().getAccountNumber(),
129 | paymentCommand.getDebtorParty().getAccountType(),
130 | paymentCommand.getDebtorParty().getAddress(),
131 | paymentCommand.getDebtorParty().getBankId(),
132 | paymentCommand.getDebtorParty().getName());
133 | }
134 |
135 | private BeneficiaryParty getBeneficiaryParty(PaymentCommand paymentCommand) {
136 | return BeneficiaryParty.create(paymentCommand.getBeneficiaryParty().getAccountName(),
137 | paymentCommand.getBeneficiaryParty().getAccountNumber(),
138 | paymentCommand.getBeneficiaryParty().getAccountType(),
139 | paymentCommand.getBeneficiaryParty().getAddress(),
140 | paymentCommand.getBeneficiaryParty().getBankId(),
141 | paymentCommand.getBeneficiaryParty().getName());
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/command/AddPaymentCommand.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.command;
2 |
3 | public class AddPaymentCommand extends PaymentCommand{
4 |
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/command/PaymentCommand.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.command;
2 |
3 | import com.politrons.application.model.payload.payload.BeneficiaryPartyPayload;
4 | import com.politrons.application.model.payload.payload.DebtorPartyPayload;
5 | import com.politrons.application.model.payload.payload.SponsorPartyPayload;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Getter;
8 | import lombok.NoArgsConstructor;
9 | import lombok.Setter;
10 |
11 | @Getter
12 | @Setter
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public abstract class PaymentCommand {
16 | protected String amount;
17 | protected String currency;
18 | protected String paymentId;
19 | protected String paymentPurpose;
20 | protected String paymentType;
21 | protected String processingDate;
22 | protected String reference;
23 | protected String schemePaymentType;
24 | protected String schemePaymentSubType;
25 | protected DebtorPartyPayload debtorParty;
26 | protected SponsorPartyPayload sponsorParty;
27 | protected BeneficiaryPartyPayload beneficiaryParty;
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/command/UpdatePaymentCommand.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.command;
2 |
3 | public class UpdatePaymentCommand extends PaymentCommand{
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/error/ErrorPayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.error;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class ErrorPayload {
13 |
14 | public int code;
15 | public String cause;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/payload/BeneficiaryPartyPayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.payload;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class BeneficiaryPartyPayload {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/payload/DebtorPartyPayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.payload;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class DebtorPartyPayload {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/payload/PaymentInfoPayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.payload;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentInfoPayload {
14 | private String amount;
15 | private String currency;
16 | private String paymentId;
17 | private String paymentPurpose;
18 | private String paymentType;
19 | private String processingDate;
20 | private String reference;
21 | private String schemePaymentSubType;
22 | private String schemePaymentType;
23 | DebtorPartyPayload debtorParty;
24 | SponsorPartyPayload sponsorParty;
25 | BeneficiaryPartyPayload beneficiaryParty;
26 |
27 | public static PaymentInfoPayload create(String amount,
28 | String currency,
29 | String paymentId,
30 | String paymentPurpose,
31 | String paymentType,
32 | String processingDate,
33 | String reference,
34 | String schemePaymentSubType,
35 | String schemePaymentType,
36 | DebtorPartyPayload debtorParty,
37 | SponsorPartyPayload sponsorParty,
38 | BeneficiaryPartyPayload beneficiaryParty) {
39 | return new PaymentInfoPayload(amount, currency, paymentId, paymentPurpose,
40 | paymentType, processingDate, reference, schemePaymentSubType,
41 | schemePaymentType, debtorParty, sponsorParty, beneficiaryParty);
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/payload/PaymentStatePayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.payload;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class PaymentStatePayload {
13 |
14 | private String id;
15 | private String type;
16 | private float version;
17 | PaymentInfoPayload paymentInfo;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/payload/SponsorPartyPayload.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.payload;
2 |
3 |
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class SponsorPartyPayload {
14 | private String accountNumber;
15 | private String bankId;
16 | private String bankIdCode;
17 | }
18 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/model/payload/response/PaymentResponse.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.model.payload.response;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class PaymentResponse {
13 |
14 | int code;
15 | T payload;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/resources/PaymentHealthCheck.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.resources;
2 |
3 | import com.politrons.infrastructure.CassandraConnector;
4 | import org.eclipse.microprofile.health.Health;
5 | import org.eclipse.microprofile.health.HealthCheck;
6 | import org.eclipse.microprofile.health.HealthCheckResponse;
7 |
8 | import javax.enterprise.context.ApplicationScoped;
9 |
10 | /**
11 | * Health check implementation in Quarkus it's so simple enough as add the annotation
12 | * @Health and @ApplicationScoped to be injected and implement [HealthCheck] which it
13 | * will force you to implement [call] method which it will return a [HealthCheckResponse]
14 | */
15 | @Health
16 | @ApplicationScoped
17 | public class PaymentHealthCheck implements HealthCheck {
18 |
19 | @Override
20 | public HealthCheckResponse call() {
21 | return HealthCheckResponse.named("Payment API health check").up()
22 | .withData("Cassandra database running:", CassandraConnector.isStarted())
23 | .build();
24 | }
25 | }
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/resources/PaymentResource.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.resources;
2 |
3 | import com.politrons.application.handler.PaymentHandler;
4 | import com.politrons.application.model.command.AddPaymentCommand;
5 | import com.politrons.application.model.command.UpdatePaymentCommand;
6 | import com.politrons.application.model.error.ErrorPayload;
7 | import com.politrons.application.model.payload.response.PaymentResponse;
8 | import com.politrons.application.service.PaymentService;
9 | import io.vavr.control.Either;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import javax.inject.Inject;
14 | import javax.ws.rs.*;
15 | import javax.ws.rs.core.MediaType;
16 | import java.util.concurrent.CompletionStage;
17 |
18 | import static io.vavr.API.*;
19 | import static io.vavr.Patterns.$Left;
20 | import static io.vavr.Patterns.$Right;
21 |
22 | @Path("/v1/payment")
23 | @Produces(MediaType.APPLICATION_JSON)
24 | @Consumes(MediaType.APPLICATION_JSON)
25 | public class PaymentResource {
26 |
27 | private Logger logger = LoggerFactory.getLogger(PaymentResource.class);
28 |
29 | @Inject
30 | PaymentService service;
31 |
32 | @Inject
33 | PaymentHandler handler;
34 |
35 | @GET
36 | @Path("/version")
37 | public String version() {
38 | logger.debug("Request to Version endpoint");
39 | return "Payment API V1.0";
40 | }
41 |
42 | /**
43 | * Endpoint to fetch a previous payment created.
44 | * We receive the id and we use to create the query to be passed into the service.
45 | * No transformation from Command into Domain into Event need it in Queries.
46 | * Only from domain into Payload to make our application not too much couple with domain.
47 | *
48 | * @param id of the payment to fetch
49 | * @return a PaymentResponse with the operation code and the payload as [PaymentStatePayload]
50 | */
51 | @GET
52 | @Path("/{paymentId}")
53 | public CompletionStage> fetchPaymentById(@PathParam("paymentId") String id) {
54 | logger.debug("Request to get Payment with id " + id);
55 | return service.fetchPayment(id)
56 | .map(either -> Match(either).of(
57 | Case($Right($()), paymentStatePayload -> new PaymentResponse<>(200, paymentStatePayload)),
58 | Case($Left($()), errorPayload -> new PaymentResponse<>(errorPayload.code, errorPayload.cause))))
59 | .toCompletableFuture();
60 | }
61 |
62 | /**
63 | * Endpoint to fetch all payments.
64 | * @return a PaymentResponse with the operation code and the payload as List of PaymentStatePayload
65 | */
66 | @GET
67 | @Path("/all")
68 | public CompletionStage> fetchAllPayment() {
69 | logger.debug("Request to get all Payments");
70 | return service.fetchAllPayments()
71 | .map(either -> Match(either).of(
72 | Case($Right($()), paymentStatePayloadList -> new PaymentResponse<>(200, paymentStatePayloadList)),
73 | Case($Left($()), errorPayload -> new PaymentResponse<>(errorPayload.code, errorPayload.cause))))
74 | .toCompletableFuture();
75 | }
76 |
77 | /**
78 | * Endpoint to persist a payment. We receive a AddPaymentCommand which after being passed into the domain layer
79 | * it's persisted using the infra layer.
80 | *
81 | * @param addPaymentCommand that contains the information of the payment to be created
82 | * @return a PaymentResponse with the operation code and the payload as eventId
83 | */
84 | @POST
85 | @Path("/")
86 | public CompletionStage> addPayment(AddPaymentCommand addPaymentCommand) {
87 | logger.debug("Request to add Payment with command " + addPaymentCommand);
88 | return handler.addPayment(addPaymentCommand)
89 | .map(this::matchEventIdResponse)
90 | .toCompletableFuture();
91 | }
92 |
93 | /**
94 | * Endpoint where er receive the paymentId from the previous event as query param, and in the body of the request
95 | * the [UpdatePaymentCommand] to create a new Event with the new data updated.
96 | *
97 | * @param paymentId of the previous event created
98 | * @param updatePaymentCommand the new data to create a new event.
99 | * @return a PaymentResponse with the operation code and the payload as eventId
100 | */
101 | @PUT
102 | @Path("/{paymentId}")
103 | public CompletionStage> updatePayment(@PathParam("paymentId") String paymentId,
104 | UpdatePaymentCommand updatePaymentCommand) {
105 | logger.debug("Request to create update Payment event for paymentId " + paymentId);
106 | updatePaymentCommand.setPaymentId(paymentId);
107 | return handler.updatePayment(updatePaymentCommand)
108 | .map(this::matchEventIdResponse)
109 | .toCompletableFuture();
110 | }
111 |
112 | /**
113 | * Endpoint where er receive the paymentId from the previous event as query param to find the previous event
114 | * change the state to [deleted] and persist as a new Event.
115 | *
116 | * @param paymentId of the previous event created
117 | * @return a PaymentResponse with the operation code and the payload as eventId
118 | */
119 | @DELETE
120 | @Path("/{paymentId}")
121 | public CompletionStage> deletePayment(@PathParam("paymentId") String paymentId) {
122 | logger.debug("Request to create delete Payment event for paymentId " + paymentId);
123 | return handler.deletePayment(paymentId)
124 | .map(this::matchEventIdResponse)
125 | .toCompletableFuture();
126 | }
127 |
128 |
129 | private PaymentResponse matchEventIdResponse(Either either) {
130 | return Match(either).of(
131 | Case($Right($()), eventId -> new PaymentResponse<>(200, eventId)),
132 | Case($Left($()), errorPayload -> new PaymentResponse<>(errorPayload.code, errorPayload.cause)));
133 | }
134 |
135 | }
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/service/PaymentService.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.service;
2 |
3 | import com.politrons.application.model.error.ErrorPayload;
4 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
5 | import com.politrons.application.service.impl.PaymentServiceImpl;
6 | import io.vavr.concurrent.Future;
7 | import io.vavr.control.Either;
8 | import ma.glasnost.orika.MapperFactory;
9 | import ma.glasnost.orika.impl.DefaultMapperFactory;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import javax.enterprise.context.ApplicationScoped;
14 | import java.util.List;
15 |
16 | @ApplicationScoped
17 | public interface PaymentService {
18 |
19 | Logger logger = LoggerFactory.getLogger(PaymentServiceImpl.class);
20 |
21 | MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
22 |
23 | Future> fetchPayment(String id);
24 |
25 | Future>> fetchAllPayments();
26 | }
27 |
--------------------------------------------------------------------------------
/application/src/main/java/com/politrons/application/service/impl/PaymentServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.service.impl;
2 |
3 |
4 | import com.politrons.application.model.error.ErrorPayload;
5 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
6 | import com.politrons.application.service.PaymentService;
7 | import com.politrons.domain.PaymentStateAggregateRoot;
8 | import com.politrons.infrastructure.dao.PaymentDAO;
9 | import com.politrons.infrastructure.events.PaymentAdded;
10 | import io.vavr.API;
11 | import io.vavr.collection.Stream;
12 | import io.vavr.concurrent.Future;
13 | import io.vavr.control.Either;
14 | import lombok.AllArgsConstructor;
15 | import lombok.NoArgsConstructor;
16 | import ma.glasnost.orika.MapperFacade;
17 |
18 | import javax.enterprise.context.ApplicationScoped;
19 | import javax.inject.Inject;
20 |
21 | import java.util.List;
22 | import java.util.stream.Collectors;
23 |
24 | import static io.vavr.API.*;
25 | import static io.vavr.Patterns.*;
26 |
27 | /**
28 | * Service to make Queries into the DAO.
29 | * Doing DDD + CQRS since we need to be fast in queries since 90% of traffic are queries, and there's no transformation need it here
30 | * we go though directly to the source instead to pass through the repository.
31 | */
32 | @AllArgsConstructor
33 | @NoArgsConstructor
34 | @ApplicationScoped
35 | public class PaymentServiceImpl implements PaymentService {
36 |
37 | @Inject
38 | PaymentDAO paymentDAO;
39 |
40 | /**
41 | * Method to fetch a payment previously created/updated/deleted.
42 | *
43 | * @param id of the payment
44 | * @return the Domain model PaymentStateAggregateRoot
45 | */
46 | public Future> fetchPayment(String id) {
47 | return paymentDAO.fetchPayment(id)
48 | .map(either -> Match(either).of(
49 | Case($Right($()), paymentStateAggregateRoot -> Right(transformPaymentStateAggregateRootToPayload(paymentStateAggregateRoot))),
50 | Case($Left($()), throwable -> {
51 | logger.error("Error in fetch payment Service. Caused by:" + throwable.getCause());
52 | return Left(new ErrorPayload(500, throwable.getMessage()));
53 | })));
54 | }
55 |
56 | /**
57 | * Method to fetch all payments made on the system
58 | * @return List of [PaymentStatePayload]
59 | */
60 | @Override
61 | public Future>> fetchAllPayments() {
62 | return paymentDAO.fetchAllPayments()
63 | .map(either -> Match(either).of(
64 | Case($Right($()), paymentStateAggregateRootList -> Right(transformPaymentStateAggregateRootListToPayload(paymentStateAggregateRootList))),
65 | Case($Left($()), throwable -> {
66 | logger.error("Error in fetch payment Service. Caused by:" + throwable.getCause());
67 | return Left(new ErrorPayload(500, throwable.getMessage()));
68 | })));
69 | }
70 |
71 | /**
72 | * Get a list of [PaymentStateAggregateRoot] and transform into a list of [PaymentStatePayload]
73 | */
74 | private List transformPaymentStateAggregateRootListToPayload(List paymentStateAggregateRootList) {
75 | return paymentStateAggregateRootList.stream()
76 | .map(this::transformPaymentStateAggregateRootToPayload)
77 | .collect(Collectors.toList());
78 | }
79 |
80 | /**
81 | * Function to transform from the domain model [PaymentStateAggregateRoot] into payload type [PaymentStatePayload]
82 | */
83 | private PaymentStatePayload transformPaymentStateAggregateRootToPayload(PaymentStateAggregateRoot paymentStateAggregateRoot) {
84 | mapperFactory.classMap(PaymentStateAggregateRoot.class, PaymentStatePayload.class);
85 | MapperFacade mapper = mapperFactory.getMapperFacade();
86 | return mapper.map(paymentStateAggregateRoot, PaymentStatePayload.class);
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/application/src/main/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/politrons/PaymentAPI/2e12a671000d26cf34fe4afb3f96b99ffff7ce65/application/src/main/resources/META-INF/beans.xml
--------------------------------------------------------------------------------
/application/src/main/resources/META-INF/microprofile-config.properties:
--------------------------------------------------------------------------------
1 | # DEBUG console logging
2 | quarkus.log.console.enable=true
3 | quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n
4 | quarkus.log.console.level=INFO
5 |
6 | # DEBUG file logging
7 | quarkus.log.level=INFO
8 | quarkus.log.min-level=INFO
9 | quarkus.log.file.enable=true
10 | quarkus.log.file.path=/tmp/log.log
11 | quarkus.log.file.level=DEBUG
12 | quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n
13 | quarkus.log.category."io.quarkus.smallrye.jwt".level=INFO
14 | quarkus.log.category."io.undertow.request.security".level=INFO
15 | quarkus.log.category."io.smallrye.jwt".level=INFO
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/it/PaymentResourceBackendDownTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.it;
2 |
3 | import com.politrons.application.JsonUtils;
4 | import com.politrons.application.model.payload.response.PaymentResponse;
5 | import io.quarkus.test.junit.QuarkusTest;
6 | import io.restassured.http.Header;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import static io.restassured.RestAssured.given;
10 | import static org.junit.jupiter.api.Assertions.assertEquals;
11 |
12 | @QuarkusTest
13 | class PaymentResourceBackendDownTest {
14 |
15 | @Test
16 | void addPaymentEndpoint() {
17 | PaymentResponse response = given()
18 | .contentType("application/json")
19 | .header(new Header("Content-Type", "application/json"))
20 | .body(JsonUtils.paymentRequest())
21 | .when().post("/v1/payment/")
22 | .then()
23 | .statusCode(200)
24 | .extract()
25 | .as(PaymentResponse.class);
26 | assertEquals(response.getCode(), 500);
27 | }
28 |
29 | @Test
30 | void updatePaymentEndpoint() {
31 | PaymentResponse response = given()
32 | .contentType("application/json")
33 | .header(new Header("Content-Type", "application/json"))
34 | .body(JsonUtils.paymentRequest())
35 | .when().put("/v1/payment/" + "foo")
36 | .then()
37 | .statusCode(200)
38 | .extract()
39 | .as(PaymentResponse.class);
40 | assertEquals(response.getCode(), 500);
41 | }
42 |
43 | @Test
44 | void deletePaymentEndpoint() {
45 | PaymentResponse response = given()
46 | .contentType("application/json")
47 | .header(new Header("Content-Type", "application/json"))
48 | .when().delete("/v1/payment/" + "bla")
49 | .then()
50 | .statusCode(200)
51 | .extract()
52 | .as(PaymentResponse.class);
53 | assertEquals(response.getCode(), 500);
54 | }
55 |
56 | @Test
57 | void fetchPaymentEndpoint() {
58 | PaymentResponse response = given()
59 | .contentType("application/json")
60 | .header(new Header("Content-Type", "application/json"))
61 | .when().get("/v1/payment/" + "foo")
62 | .then()
63 | .statusCode(200)
64 | .extract()
65 | .as(PaymentResponse.class);
66 | assertEquals(response.getCode(), 500);
67 | }
68 |
69 | @Test
70 | void fetchAllPaymentEndpoint() {
71 | PaymentResponse response = given()
72 | .contentType("application/json")
73 | .header(new Header("Content-Type", "application/json"))
74 | .when().get("/v1/payment/all")
75 | .then()
76 | .statusCode(200)
77 | .extract()
78 | .as(PaymentResponse.class);
79 | assertEquals(response.getCode(), 500);
80 | }
81 |
82 |
83 | }
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/it/PaymentResourceTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.it;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.core.type.TypeReference;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.politrons.application.JsonUtils;
7 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
8 | import com.politrons.application.model.payload.response.PaymentResponse;
9 | import com.politrons.infrastructure.CassandraConnector;
10 | import com.politrons.infrastructure.dto.BeneficiaryPartyDTO;
11 | import com.politrons.infrastructure.dto.DebtorPartyDTO;
12 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
13 | import com.politrons.infrastructure.dto.SponsorPartyDTO;
14 | import com.politrons.infrastructure.events.PaymentAdded;
15 | import io.quarkus.test.junit.QuarkusTest;
16 | import io.restassured.http.Header;
17 | import org.junit.jupiter.api.AfterAll;
18 | import org.junit.jupiter.api.BeforeAll;
19 | import org.junit.jupiter.api.Test;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 | import java.util.UUID;
24 |
25 | import static io.restassured.RestAssured.given;
26 | import static org.hamcrest.CoreMatchers.is;
27 | import static org.junit.jupiter.api.Assertions.*;
28 |
29 | @QuarkusTest
30 | class PaymentResourceTest {
31 |
32 | private ObjectMapper mapper = new ObjectMapper();
33 |
34 | @BeforeAll
35 | static void init() {
36 | CassandraConnector.start();
37 | }
38 |
39 | @AfterAll
40 | static void stop() {
41 | CassandraConnector.stop();
42 | }
43 |
44 | @Test
45 | void versionEndpoint() {
46 | given()
47 | .when().get("/v1/payment/version")
48 | .then()
49 | .statusCode(200)
50 | .body(is("Payment API V1.0"));
51 | }
52 |
53 | @Test
54 | void addPaymentEndpoint() {
55 | PaymentResponse response = given()
56 | .contentType("application/json")
57 | .header(new Header("Content-Type", "application/json"))
58 | .body(JsonUtils.paymentRequest())
59 | .when().post("/v1/payment/")
60 | .then()
61 | .statusCode(200)
62 | .extract()
63 | .as(PaymentResponse.class);
64 | assertEquals(response.getCode(), 200);
65 | assertTrue(response.getPayload() instanceof String);
66 | assertFalse(((String) response.getPayload()).isEmpty());
67 |
68 | }
69 |
70 | @Test
71 | void updatePaymentEndpoint() throws JsonProcessingException {
72 | String uuid = addMockPayment();
73 | PaymentResponse response = given()
74 | .contentType("application/json")
75 | .header(new Header("Content-Type", "application/json"))
76 | .body(JsonUtils.paymentRequest())
77 | .when().put("/v1/payment/" + uuid)
78 | .then()
79 | .statusCode(200)
80 | .extract()
81 | .as(PaymentResponse.class);
82 | assertEquals(response.getCode(), 200);
83 | assertTrue(response.getPayload() instanceof String);
84 | assertFalse(((String) response.getPayload()).isEmpty());
85 | }
86 |
87 | @Test
88 | void deletePaymentEndpoint() throws JsonProcessingException {
89 | String uuid = addMockPayment();
90 | PaymentResponse response = given()
91 | .contentType("application/json")
92 | .header(new Header("Content-Type", "application/json"))
93 | .when().delete("/v1/payment/" + uuid)
94 | .then()
95 | .statusCode(200)
96 | .extract()
97 | .as(PaymentResponse.class);
98 | assertEquals(response.getCode(), 200);
99 | assertTrue(response.getPayload() instanceof String);
100 | assertFalse(((String) response.getPayload()).isEmpty());
101 | }
102 |
103 | @Test
104 | void fetchPaymentEndpoint() throws JsonProcessingException {
105 | String uuid = addMockPayment();
106 | PaymentResponse response = given()
107 | .contentType("application/json")
108 | .header(new Header("Content-Type", "application/json"))
109 | .when().get("/v1/payment/" + uuid)
110 | .then()
111 | .statusCode(200)
112 | .extract()
113 | .as(PaymentResponse.class);
114 | assertEquals(response.getCode(), 200);
115 | PaymentStatePayload paymentStatePayload = mapper.convertValue(response.getPayload(), PaymentStatePayload.class);
116 | assertEquals(paymentStatePayload.getType(), "created");
117 | }
118 |
119 | @Test
120 | void fetchPaymentEndpointWithWrongEventId() {
121 | PaymentResponse response = given()
122 | .contentType("application/json")
123 | .header(new Header("Content-Type", "application/json"))
124 | .when().get("/v1/payment/" + "foo")
125 | .then()
126 | .statusCode(200)
127 | .extract()
128 | .as(PaymentResponse.class);
129 | assertEquals(response.getCode(), 500);
130 | }
131 |
132 | @Test
133 | void fetchAllPaymentEndpoint() throws JsonProcessingException {
134 | addMockPayment();
135 | addMockPayment();
136 | addMockPayment();
137 | PaymentResponse response = given()
138 | .contentType("application/json")
139 | .header(new Header("Content-Type", "application/json"))
140 | .when().get("/v1/payment/all")
141 | .then()
142 | .statusCode(200)
143 | .extract()
144 | .as(PaymentResponse.class);
145 | assertEquals(response.getCode(), 200);
146 | List paymentStatePayloadList = mapper.convertValue(response.getPayload(), new TypeReference>() {
147 | });
148 | assertEquals(paymentStatePayloadList.size(), 3);
149 | assertNotNull(paymentStatePayloadList.get(0).getPaymentInfo());
150 | }
151 |
152 | private String addMockPayment() throws JsonProcessingException {
153 | String uuid = UUID.randomUUID().toString();
154 | PaymentAdded paymentAddedEvent = getPaymentAddedEvent();
155 | paymentAddedEvent.setId(uuid);
156 | String event = mapper.writeValueAsString(paymentAddedEvent);
157 | CassandraConnector.addPayment(getAddPaymentQuery(paymentAddedEvent, "12345", event));
158 | return uuid;
159 | }
160 |
161 |
162 | String getAddPaymentQuery(PaymentAdded paymentAdded, String timeStampMillis, String event) {
163 | return "INSERT INTO " + "paymentsSchema.payment" +
164 | "(id, timestamp, event) " +
165 | "VALUES (" + paymentAdded.getId() + ", '" +
166 | timeStampMillis + "', '" +
167 | event + "');";
168 | }
169 |
170 | PaymentAdded getPaymentAddedEvent() {
171 | return new PaymentAdded(UUID.randomUUID().toString(), "created", 0, getPaymentInfoDTO());
172 | }
173 |
174 | PaymentInfoDTO getPaymentInfoDTO() {
175 | DebtorPartyDTO debtorParty = getDebtorParty();
176 | BeneficiaryPartyDTO beneficiaryParty = getBeneficiaryParty();
177 | SponsorPartyDTO sponsorParty = getSponsorParty();
178 | return new PaymentInfoDTO("amount", "currency",
179 | "paymentId",
180 | "paymentPurpose", "paymentType",
181 | "processingDate", "reference", "schemePaymentSubType",
182 | "schemePaymentType", debtorParty, sponsorParty, beneficiaryParty);
183 | }
184 |
185 | private SponsorPartyDTO getSponsorParty() {
186 | return new SponsorPartyDTO("accountName",
187 | "bankId", "bankCode");
188 | }
189 |
190 | private BeneficiaryPartyDTO getBeneficiaryParty() {
191 | return new BeneficiaryPartyDTO("accountName",
192 | "accountNumber", 0, "address", "bankId", "name");
193 | }
194 |
195 | private DebtorPartyDTO getDebtorParty() {
196 | return new DebtorPartyDTO("accountName", "accountNumber",
197 | 0, "address", "bankId", "name");
198 | }
199 |
200 | }
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/performance/PaymentResourcePerformanceTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.performance;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.politrons.application.utils.PaymentResourceTestUtils;
5 | import com.politrons.infrastructure.CassandraConnector;
6 | import io.quarkus.test.junit.QuarkusTest;
7 | import org.junit.jupiter.api.AfterAll;
8 | import org.junit.jupiter.api.BeforeAll;
9 | import org.junit.jupiter.api.RepeatedTest;
10 |
11 | @QuarkusTest
12 | class PaymentResourcePerformanceTest extends PaymentResourceTestUtils {
13 |
14 | private final int numOfRequest = 100;//It should be at least 1000
15 |
16 | @BeforeAll
17 | static void init() {
18 | CassandraConnector.start();
19 | }
20 |
21 | @AfterAll
22 | static void stop() {
23 | CassandraConnector.stop();
24 | }
25 |
26 | @RepeatedTest(value = numOfRequest, name = RepeatedTest.LONG_DISPLAY_NAME)
27 | void performanceTest() throws JsonProcessingException {
28 | addPayment();
29 | updatePayment();
30 | deletePaymentEndpoint();
31 | fetchPaymentEndpoint();
32 | fetchAllPaymentEndpoint();
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/unit/CommandsTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.unit;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.politrons.application.JsonUtils;
5 | import com.politrons.application.model.command.AddPaymentCommand;
6 | import com.politrons.application.model.command.UpdatePaymentCommand;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import java.io.IOException;
10 |
11 | import static org.junit.jupiter.api.Assertions.assertNotNull;
12 |
13 | class CommandsTest {
14 |
15 | private ObjectMapper mapper = new ObjectMapper();
16 |
17 | @Test
18 | void transformJsonToAddPaymentCommand() throws IOException {
19 | AddPaymentCommand command = mapper.readValue(JsonUtils.paymentRequest(), AddPaymentCommand.class);
20 | assertNotNull(command);
21 | }
22 |
23 | @Test
24 | void transformJsonToUpdatePaymentCommand() throws IOException {
25 | UpdatePaymentCommand command = mapper.readValue(JsonUtils.paymentRequest(), UpdatePaymentCommand.class);
26 | assertNotNull(command);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/unit/PaymentHandlerTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.unit;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.politrons.application.JsonUtils;
5 | import com.politrons.application.handler.PaymentHandler;
6 | import com.politrons.application.handler.impl.PaymentHandlerImpl;
7 | import com.politrons.application.model.command.AddPaymentCommand;
8 | import com.politrons.application.model.command.UpdatePaymentCommand;
9 | import com.politrons.application.model.error.ErrorPayload;
10 | import com.politrons.domain.PaymentStateAggregateRoot;
11 | import com.politrons.infrastructure.repository.PaymentRepository;
12 | import io.vavr.concurrent.Future;
13 | import io.vavr.control.Either;
14 | import org.junit.jupiter.api.BeforeEach;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.extension.ExtendWith;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.jupiter.MockitoExtension;
19 |
20 | import java.io.IOException;
21 |
22 | import static io.vavr.API.Right;
23 | import static org.junit.jupiter.api.Assertions.assertFalse;
24 | import static org.junit.jupiter.api.Assertions.assertTrue;
25 | import static org.mockito.ArgumentMatchers.any;
26 | import static org.mockito.Mockito.when;
27 |
28 | @ExtendWith(MockitoExtension.class)
29 | class PaymentHandlerTest {
30 |
31 | @Mock
32 | PaymentRepository paymentRepository;
33 |
34 | private PaymentHandler paymentHandler;
35 |
36 | @BeforeEach
37 | void setup() {
38 | paymentHandler = new PaymentHandlerImpl(paymentRepository);
39 | }
40 |
41 | private ObjectMapper mapper = new ObjectMapper();
42 |
43 | @Test
44 | void addPaymentHandler() throws IOException {
45 | when(paymentRepository.addPayment(any(PaymentStateAggregateRoot.class))).thenReturn(Future.of(() -> Right("1981")));
46 | AddPaymentCommand addPaymentCommand = mapper.readValue(JsonUtils.paymentRequest(), AddPaymentCommand.class);
47 | Future> eithers = paymentHandler.addPayment(addPaymentCommand);
48 | assertTrue(eithers.get().isRight());
49 | assertFalse(eithers.get().right().get().isEmpty());
50 | }
51 |
52 | @Test
53 | void updatePaymentHandler() throws IOException {
54 | when(paymentRepository.updatePayment(any(PaymentStateAggregateRoot.class))).thenReturn(Future.of(() -> Right("1981")));
55 | UpdatePaymentCommand updatePaymentCommand = mapper.readValue(JsonUtils.paymentRequest(), UpdatePaymentCommand.class);
56 | Future> eithers = paymentHandler.updatePayment(updatePaymentCommand);
57 | assertTrue(eithers.get().isRight());
58 | assertFalse(eithers.get().right().get().isEmpty());
59 | }
60 |
61 | @Test
62 | void deletePaymentHandler() {
63 | when(paymentRepository.fetchPayment(any(String.class))).thenReturn(Future.of(() -> Right(new PaymentStateAggregateRoot())));
64 | when(paymentRepository.deletePayment(any(PaymentStateAggregateRoot.class))).thenReturn(Future.of(() -> Right("1981")));
65 | Future> eithers = paymentHandler.deletePayment("123");
66 | assertTrue(eithers.get().isRight());
67 | assertFalse(eithers.get().right().get().isEmpty());
68 | }
69 |
70 | @Test
71 | void deletePaymentHandlerWithWrongEventId() {
72 | when(paymentRepository.fetchPayment(any(String.class))).thenReturn(Future.failed(new IllegalArgumentException()));
73 | Future> eithers = paymentHandler.deletePayment("123");
74 | assertTrue(eithers.get().isLeft());
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/unit/PaymentServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.unit;
2 |
3 | import com.politrons.application.model.error.ErrorPayload;
4 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
5 | import com.politrons.application.model.payload.response.PaymentResponse;
6 | import com.politrons.application.service.PaymentService;
7 | import com.politrons.application.service.impl.PaymentServiceImpl;
8 | import com.politrons.domain.PaymentStateAggregateRoot;
9 | import com.politrons.infrastructure.dao.PaymentDAO;
10 | import io.vavr.concurrent.Future;
11 | import io.vavr.control.Either;
12 | import jodd.util.ArraysUtil;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 | import org.junit.jupiter.api.extension.ExtendWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.jupiter.MockitoExtension;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import static io.vavr.API.*;
23 | import static org.junit.jupiter.api.Assertions.assertEquals;
24 | import static org.junit.jupiter.api.Assertions.assertTrue;
25 | import static org.mockito.ArgumentMatchers.any;
26 | import static org.mockito.Mockito.when;
27 |
28 | @ExtendWith(MockitoExtension.class)
29 | class PaymentServiceTest {
30 |
31 | @Mock
32 | PaymentDAO paymentDAO;
33 |
34 | private PaymentService paymentService;
35 |
36 | @BeforeEach
37 | void setup() {
38 | paymentService = new PaymentServiceImpl(paymentDAO);
39 | }
40 |
41 | @Test
42 | void fetchPaymentService() {
43 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("myCustomUUID", "payment", 0, null);
44 | when(paymentDAO.fetchPayment(any(String.class))).thenReturn(Future.of(() -> Right(paymentStateAggregateRoot)));
45 | Future> eithers = paymentService.fetchPayment("myCustomUUID");
46 | assertTrue(eithers.get().isRight());
47 | assertEquals(eithers.get().right().get().getId(), "myCustomUUID");
48 | }
49 |
50 | @Test
51 | void fetchPaymentServiceError() {
52 | when(paymentDAO.fetchPayment(any(String.class))).thenReturn(Future.of(() -> Left(new IllegalArgumentException())));
53 | Future> eithers = paymentService.fetchPayment("1981");
54 | assertTrue(eithers.get().isLeft());
55 | assertTrue(eithers.get().left().get() instanceof ErrorPayload);
56 | }
57 |
58 |
59 | @Test
60 | void fetchAllPaymentService() {
61 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("myCustomUUID", "payment", 0, null);
62 | List paymentStateAggregateRootList = new ArrayList();
63 | paymentStateAggregateRootList.add(paymentStateAggregateRoot);
64 | when(paymentDAO.fetchAllPayments()).thenReturn(Future.of(() -> Right(paymentStateAggregateRootList)));
65 | Future>> eithers = paymentService.fetchAllPayments();
66 | assertTrue(eithers.get().isRight());
67 | assertEquals(eithers.get().right().get().size(), 1);
68 | assertEquals(eithers.get().right().get().get(0).getId(), "myCustomUUID");
69 |
70 | }
71 |
72 | @Test
73 | void fetchAllPaymentServiceError() {
74 | when(paymentDAO.fetchAllPayments()).thenReturn(Future.of(() -> Left(new IllegalArgumentException())));
75 | Future>> eithers = paymentService.fetchAllPayments();
76 | assertTrue(eithers.get().isLeft());
77 | assertTrue(eithers.get().left().get() instanceof ErrorPayload);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/utils/PaymentResourceTestUtils.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.utils;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.core.type.TypeReference;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.politrons.application.JsonUtils;
7 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
8 | import com.politrons.application.model.payload.response.PaymentResponse;
9 | import com.politrons.infrastructure.CassandraConnector;
10 | import com.politrons.infrastructure.dto.BeneficiaryPartyDTO;
11 | import com.politrons.infrastructure.dto.DebtorPartyDTO;
12 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
13 | import com.politrons.infrastructure.dto.SponsorPartyDTO;
14 | import com.politrons.infrastructure.events.PaymentAdded;
15 | import io.restassured.http.Header;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.UUID;
20 |
21 | import static io.restassured.RestAssured.given;
22 | import static org.junit.jupiter.api.Assertions.*;
23 | import static org.junit.jupiter.api.Assertions.assertNotNull;
24 |
25 | public class PaymentResourceTestUtils {
26 |
27 | ObjectMapper mapper = new ObjectMapper();
28 |
29 | protected void updatePayment() throws JsonProcessingException {
30 | String uuid = addMockPayment();
31 | PaymentResponse response = given()
32 | .contentType("application/json")
33 | .header(new Header("Content-Type", "application/json"))
34 | .body(JsonUtils.paymentRequest())
35 | .when().put("/v1/payment/" + uuid)
36 | .then()
37 | .statusCode(200)
38 | .extract()
39 | .as(PaymentResponse.class);
40 | assertEquals(response.getCode(), 200);
41 | assertTrue(response.getPayload() instanceof String);
42 | assertFalse(((String) response.getPayload()).isEmpty());
43 | }
44 |
45 | protected void addPayment() {
46 | PaymentResponse response = given()
47 | .contentType("application/json")
48 | .header(new Header("Content-Type", "application/json"))
49 | .body(JsonUtils.paymentRequest())
50 | .when().post("/v1/payment/")
51 | .then()
52 | .statusCode(200)
53 | .extract()
54 | .as(PaymentResponse.class);
55 | assertEquals(response.getCode(), 200);
56 | assertTrue(response.getPayload() instanceof String);
57 | assertFalse(((String) response.getPayload()).isEmpty());
58 | }
59 |
60 | protected void deletePaymentEndpoint() throws JsonProcessingException {
61 | String uuid = addMockPayment();
62 | PaymentResponse response = given()
63 | .contentType("application/json")
64 | .header(new Header("Content-Type", "application/json"))
65 | .when().delete("/v1/payment/" + uuid)
66 | .then()
67 | .statusCode(200)
68 | .extract()
69 | .as(PaymentResponse.class);
70 | assertEquals(response.getCode(), 200);
71 | assertTrue(response.getPayload() instanceof String);
72 | assertFalse(((String) response.getPayload()).isEmpty());
73 | }
74 |
75 | protected void fetchPaymentEndpoint() throws JsonProcessingException {
76 | String uuid = addMockPayment();
77 | PaymentResponse response = given()
78 | .contentType("application/json")
79 | .header(new Header("Content-Type", "application/json"))
80 | .when().get("/v1/payment/" + uuid)
81 | .then()
82 | .statusCode(200)
83 | .extract()
84 | .as(PaymentResponse.class);
85 | assertEquals(response.getCode(), 200);
86 | PaymentStatePayload paymentStatePayload = mapper.convertValue(response.getPayload(), PaymentStatePayload.class);
87 | assertEquals(paymentStatePayload.getType(), "created");
88 | }
89 |
90 | protected void fetchAllPaymentEndpoint() {
91 | PaymentResponse response = given()
92 | .contentType("application/json")
93 | .header(new Header("Content-Type", "application/json"))
94 | .when().get("/v1/payment/all")
95 | .then()
96 | .statusCode(200)
97 | .extract()
98 | .as(PaymentResponse.class);
99 | assertEquals(response.getCode(), 200);
100 | List paymentStatePayloadList = mapper.convertValue(response.getPayload(), new TypeReference>() {
101 | });
102 | assertTrue(paymentStatePayloadList.size() > 0);
103 | assertNotNull(paymentStatePayloadList.get(0).getPaymentInfo());
104 | }
105 |
106 |
107 | private String addMockPayment() throws JsonProcessingException {
108 | String uuid = UUID.randomUUID().toString();
109 | PaymentAdded paymentAddedEvent = getPaymentAddedEvent();
110 | paymentAddedEvent.setId(uuid);
111 | String event = mapper.writeValueAsString(paymentAddedEvent);
112 | CassandraConnector.addPayment(getAddPaymentQuery(paymentAddedEvent, "12345", event));
113 | return uuid;
114 | }
115 |
116 |
117 | String getAddPaymentQuery(PaymentAdded paymentAdded, String timeStampMillis, String event) {
118 | return "INSERT INTO " + "paymentsSchema.payment" +
119 | "(id, timestamp, event) " +
120 | "VALUES (" + paymentAdded.getId() + ", '" +
121 | timeStampMillis + "', '" +
122 | event + "');";
123 | }
124 |
125 | PaymentAdded getPaymentAddedEvent() {
126 | return new PaymentAdded(UUID.randomUUID().toString(), "created", 0, getPaymentInfoDTO());
127 | }
128 |
129 | PaymentInfoDTO getPaymentInfoDTO() {
130 | DebtorPartyDTO debtorParty = getDebtorParty();
131 | BeneficiaryPartyDTO beneficiaryParty = getBeneficiaryParty();
132 | SponsorPartyDTO sponsorParty = getSponsorParty();
133 | return new PaymentInfoDTO("amount", "currency",
134 | "paymentId",
135 | "paymentPurpose", "paymentType",
136 | "processingDate", "reference", "schemePaymentSubType",
137 | "schemePaymentType", debtorParty, sponsorParty, beneficiaryParty);
138 | }
139 |
140 | private SponsorPartyDTO getSponsorParty() {
141 | return new SponsorPartyDTO("accountName",
142 | "bankId", "bankCode");
143 | }
144 |
145 | private BeneficiaryPartyDTO getBeneficiaryParty() {
146 | return new BeneficiaryPartyDTO("accountName",
147 | "accountNumber", 0, "address", "bankId", "name");
148 | }
149 |
150 | private DebtorPartyDTO getDebtorParty() {
151 | return new DebtorPartyDTO("accountName", "accountNumber",
152 | 0, "address", "bankId", "name");
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/application/src/test/java/com/politrons/application/volume/PaymentResourceVolumeTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.application.volume;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.core.type.TypeReference;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.politrons.application.JsonUtils;
7 | import com.politrons.application.model.payload.payload.PaymentStatePayload;
8 | import com.politrons.application.model.payload.response.PaymentResponse;
9 | import com.politrons.application.utils.PaymentResourceTestUtils;
10 | import com.politrons.infrastructure.CassandraConnector;
11 | import com.politrons.infrastructure.dto.BeneficiaryPartyDTO;
12 | import com.politrons.infrastructure.dto.DebtorPartyDTO;
13 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
14 | import com.politrons.infrastructure.dto.SponsorPartyDTO;
15 | import com.politrons.infrastructure.events.PaymentAdded;
16 | import io.quarkus.test.junit.QuarkusTest;
17 | import io.restassured.http.Header;
18 | import org.junit.jupiter.api.AfterAll;
19 | import org.junit.jupiter.api.BeforeAll;
20 | import org.junit.jupiter.api.RepeatedTest;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.UUID;
25 |
26 | import static io.restassured.RestAssured.given;
27 | import static org.junit.jupiter.api.Assertions.*;
28 |
29 | /**
30 | * Volume test are meant to be used to have a load of traffic for a big period of time to detect
31 | * some possible memory leaks in your application that, it might provoke that your application get
32 | * out of memory and die.
33 | */
34 | @QuarkusTest
35 | class PaymentResourceVolumeTest extends PaymentResourceTestUtils {
36 |
37 | private final int numOfRequest = 100;//This it should be at least 10k
38 |
39 | @BeforeAll
40 | static void init() {
41 | CassandraConnector.start();
42 | }
43 |
44 | @AfterAll
45 | static void stop() {
46 | CassandraConnector.stop();
47 | }
48 |
49 | @RepeatedTest(value = numOfRequest, name = RepeatedTest.LONG_DISPLAY_NAME)
50 | void volumeTest() throws JsonProcessingException {
51 | addPayment();
52 | updatePayment();
53 | deletePaymentEndpoint();
54 | fetchPaymentEndpoint();
55 | fetchAllPaymentEndpoint();
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/application/src/test/scala/com/politrons/application/JsonUtils.scala:
--------------------------------------------------------------------------------
1 | package com.politrons.application
2 |
3 | object JsonUtils {
4 |
5 | val paymentRequest =
6 | """
7 | |{
8 | | "amount": "amount",
9 | | "currency": "currency",
10 | | "paymentId": "paymentId",
11 | | "debtorParty": {
12 | | "accountName": "accountName",
13 | | "accountNumber": "accountNumber",
14 | | "accountType": 0.0,
15 | | "address": "address",
16 | | "bankId": "bankId",
17 | | "name": "name"
18 | | },
19 | | "sponsorParty": {
20 | | "accountNumber": "accountName",
21 | | "bankId": "bankId",
22 | | "bankIdCode": "bankCode"
23 | | },
24 | | "beneficiaryParty": {
25 | | "accountName": "accountName",
26 | | "accountNumber": "accountNumber",
27 | | "accountType": 0.0,
28 | | "address": "address",
29 | | "bankId": "bankId",
30 | | "name": "name"
31 | | },
32 | | "paymentPurpose": "paymentPurpose",
33 | | "paymentType": "paymentType",
34 | | "processingDate": "processingDate",
35 | | "reference": "reference",
36 | | "schemePaymentSubType": "schemePaymentSubType",
37 | | "schemePaymentType": "schemePaymentType"
38 | |}
39 | """.stripMargin
40 | val hello =
41 | """
42 | |{"message":"hello"}
43 | """.stripMargin
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/domain/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | paymentAPI
7 | org.acme
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | domain
13 |
14 |
15 |
16 | net.alchim31.maven
17 | scala-maven-plugin
18 |
19 |
20 | scala-compile-first
21 | process-resources
22 |
23 | add-source
24 | compile
25 |
26 |
27 |
28 | scala-test-compile
29 | process-test-resources
30 |
31 | testCompile
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/politrons/domain/PaymentStateAggregateRoot.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain;
2 |
3 | import com.politrons.domain.entities.PaymentInfo;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | import java.util.UUID;
10 |
11 | @Getter
12 | @Setter
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class PaymentStateAggregateRoot {
16 |
17 | private String id;
18 | private String type;
19 | private float version;
20 | PaymentInfo paymentInfo;
21 |
22 | /**
23 | * Factory to create a PaymentStateAggregateRoot with state created.
24 | * Since this is the first event for a payment we set the rowId also as paymentId
25 | */
26 | public static PaymentStateAggregateRoot create(PaymentInfo paymentInfo) {
27 | String id = UUID.randomUUID().toString();
28 | paymentInfo.setPaymentId(id);
29 | return new PaymentStateAggregateRoot(id, "created", 0, paymentInfo);
30 | }
31 |
32 | /**
33 | * Factory to create a PaymentStateAggregateRoot with state changed
34 | */
35 | public static PaymentStateAggregateRoot update(PaymentInfo paymentInfo) {
36 | String id = UUID.randomUUID().toString();
37 | return new PaymentStateAggregateRoot(id, "changed", 0, paymentInfo);
38 | }
39 |
40 | /**
41 | * Factory to update a previous PaymentStateAggregateRoot with state deleted
42 | * In case of delete since we search previously the domain, we just need to update the rowId and set the state.
43 | */
44 | public static PaymentStateAggregateRoot delete(PaymentStateAggregateRoot paymentStateAggregateRoot) {
45 | String id = UUID.randomUUID().toString();
46 | paymentStateAggregateRoot.setId(id);
47 | paymentStateAggregateRoot.setType("deleted");
48 | return paymentStateAggregateRoot;
49 | }
50 |
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/politrons/domain/entities/BeneficiaryParty.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class BeneficiaryParty {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 |
20 | public static BeneficiaryParty create(String accountName,
21 | String accountNumber,
22 | float accountType,
23 | String address,
24 | String bankId,
25 | String name) {
26 | return new BeneficiaryParty(accountName, accountNumber, accountType, address, bankId, name);
27 |
28 | }
29 | }
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/politrons/domain/entities/DebtorParty.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class DebtorParty {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 |
20 | public static DebtorParty create(String accountName,
21 | String accountNumber,
22 | float accountType,
23 | String address,
24 | String bankId,
25 | String name) {
26 | return new DebtorParty(accountName, accountNumber, accountType, address, bankId, name);
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/politrons/domain/entities/PaymentInfo.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentInfo {
14 | private String amount;
15 | private String currency;
16 | private String paymentId;
17 | private String paymentPurpose;
18 | private String paymentType;
19 | private String processingDate;
20 | private String reference;
21 | private String schemePaymentSubType;
22 | private String schemePaymentType;
23 | DebtorParty debtorParty;
24 | SponsorParty sponsorParty;
25 | BeneficiaryParty beneficiaryParty;
26 |
27 | public static PaymentInfo create(String amount,
28 | String currency,
29 | String paymentId,
30 | String paymentPurpose,
31 | String paymentType,
32 | String processingDate,
33 | String reference,
34 | String schemePaymentSubType,
35 | String schemePaymentType,
36 | DebtorParty debtorParty,
37 | SponsorParty sponsorParty,
38 | BeneficiaryParty beneficiaryParty) {
39 | return new PaymentInfo(amount, currency, paymentId, paymentPurpose,
40 | paymentType, processingDate, reference, schemePaymentSubType,
41 | schemePaymentType, debtorParty, sponsorParty, beneficiaryParty);
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/politrons/domain/entities/SponsorParty.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities;
2 |
3 |
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class SponsorParty {
14 | private String accountNumber;
15 | private String bankId;
16 | private String bankIdCode;
17 |
18 | public static SponsorParty create(String accountNumber,
19 | String bankId,
20 | String bankIdCode) {
21 | return new SponsorParty(accountNumber, bankId, bankIdCode);
22 |
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/main/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/politrons/PaymentAPI/2e12a671000d26cf34fe4afb3f96b99ffff7ce65/domain/src/main/resources/META-INF/beans.xml
--------------------------------------------------------------------------------
/domain/src/test/java/com/politrons/domain/entities/EntitiesTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.politrons.domain.PaymentStateAggregateRoot;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import java.io.IOException;
9 |
10 | import static org.junit.jupiter.api.Assertions.*;
11 |
12 | class EntitiesTest {
13 |
14 | private ObjectMapper mapper = new ObjectMapper();
15 |
16 | @Test
17 | void createPaymentAggregateRoot() {
18 | PaymentInfo paymentInfo = getPaymentInfo();
19 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("id", "type", 1, paymentInfo);
20 | assertEquals(paymentStateAggregateRoot.getId(), "id");
21 | }
22 |
23 | @Test
24 | void transformDomainToJson() throws JsonProcessingException {
25 | PaymentInfo paymentInfo = getPaymentInfo();
26 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("id", "type", 1, paymentInfo);
27 | String json = mapper.writeValueAsString(paymentStateAggregateRoot);
28 | assertNotNull(json);
29 | }
30 |
31 | @Test
32 | void transformJsonToDomain() throws IOException {
33 | PaymentInfo paymentInfo = mapper.readValue(JsonUtils.paymentInfoJson(), PaymentInfo.class);
34 | assertNotNull(paymentInfo);
35 | }
36 |
37 | @Test
38 | void debtorPartyFactory() {
39 | DebtorParty debtorParty = DebtorParty.create("accountName", "accountNumber",
40 | 0, "address", "bankId", "name");
41 | assertNotNull(debtorParty);
42 | }
43 |
44 | @Test
45 | void beneficiaryPartyFactory() {
46 | BeneficiaryParty beneficiaryParty = BeneficiaryParty.create("accountName",
47 | "accountNumber", 0, "address", "bankId", "name");
48 | assertNotNull(beneficiaryParty);
49 |
50 | }
51 |
52 | @Test
53 | void sponsorPartyFactory() {
54 | SponsorParty sponsorParty = SponsorParty.create("accountName",
55 | "bankId", "bankCode");
56 | assertNotNull(sponsorParty);
57 | }
58 |
59 | @Test
60 | void paymentInfoFactory() {
61 | PaymentInfo paymentInfo = PaymentInfo.create("amount", "currency",
62 | "paymentId",
63 | "paymentPurpose", "paymentType",
64 | "processingDate", "reference", "schemePaymentSubType",
65 | "schemePaymentType", getDebtorParty(), getSponsorParty(), getBeneficiaryParty());
66 | assertNotNull(paymentInfo);
67 | }
68 |
69 | private PaymentInfo getPaymentInfo() {
70 | DebtorParty debtorParty = getDebtorParty();
71 | BeneficiaryParty beneficiaryParty = getBeneficiaryParty();
72 | SponsorParty sponsorParty = getSponsorParty();
73 | return new PaymentInfo("amount", "currency",
74 | "paymentId",
75 | "paymentPurpose", "paymentType",
76 | "processingDate", "reference", "schemePaymentSubType",
77 | "schemePaymentType", debtorParty, sponsorParty, beneficiaryParty);
78 | }
79 |
80 | private SponsorParty getSponsorParty() {
81 | return new SponsorParty("accountName",
82 | "bankId", "bankCode");
83 | }
84 |
85 | private BeneficiaryParty getBeneficiaryParty() {
86 | return new BeneficiaryParty("accountName",
87 | "accountNumber", 0, "address", "bankId", "name");
88 | }
89 |
90 | private DebtorParty getDebtorParty() {
91 | return new DebtorParty("accountName", "accountNumber",
92 | 0, "address", "bankId", "name");
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/domain/src/test/scala/com/politrons/domain/entities/JsonUtils.scala:
--------------------------------------------------------------------------------
1 | package com.politrons.domain.entities
2 |
3 | object JsonUtils {
4 |
5 | val paymentInfoJson =
6 | """
7 | |{"amount":"amount","currency":"currency","paymentId":"paymentId","debtorParty":{"accountName":"accountName","accountNumber":"accountNumber","accountType":0.0,"address":"address","bankId":"bankId","name":"name"},"sponsorParty":{"accountNumber":"accountName","bankId":"bankId","bankIdCode":"bankCode"},"beneficiaryParty":{"accountName":"accountName","accountNumber":"accountNumber","accountType":0.0,"address":"address","bankId":"bankId","name":"name"},"paymentPurpose":"paymentPurpose","paymentType":"paymentType","processingDate":"processingDate","reference":"reference","schemePaymentSubType":"schemePaymentSubType","schemePaymentType":"schemePaymentType"}
8 | """.stripMargin
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/img/ddd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/politrons/PaymentAPI/2e12a671000d26cf34fe4afb3f96b99ffff7ce65/img/ddd.png
--------------------------------------------------------------------------------
/img/testPyramid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/politrons/PaymentAPI/2e12a671000d26cf34fe4afb3f96b99ffff7ce65/img/testPyramid.png
--------------------------------------------------------------------------------
/infrastructure/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | paymentAPI
7 | org.acme
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | infrastructure
13 |
14 |
15 |
16 | net.alchim31.maven
17 | scala-maven-plugin
18 |
19 |
20 | scala-compile-first
21 | process-resources
22 |
23 | add-source
24 | compile
25 |
26 |
27 |
28 | scala-test-compile
29 | process-test-resources
30 |
31 | testCompile
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.acme
41 | domain
42 | 1.0-SNAPSHOT
43 |
44 |
45 | ma.glasnost.orika
46 | orika-core
47 | 1.5.2
48 |
49 |
50 |
51 | com.datastax.cassandra
52 | cassandra-driver-core
53 |
55 | 3.7.1
56 | shaded
57 |
58 |
59 | *
60 | io.netty
61 |
62 |
63 |
64 |
65 |
66 | com.datastax.cassandra
67 | cassandra-driver-mapping
68 |
70 | 3.7.1
71 |
72 |
73 | *
74 | io.netty
75 |
76 |
77 |
78 |
79 |
80 | com.datastax.cassandra
81 | cassandra-driver-extras
82 |
84 | 3.7.1
85 |
86 |
87 | *
88 | io.netty
89 |
90 |
91 |
92 |
93 |
94 | com.github.nosan
95 | embedded-cassandra
96 | 2.0.1
97 |
98 |
99 |
100 | org.mockito
101 | mockito-core
102 | 2.23.0
103 | test
104 |
105 |
106 | org.mockito
107 | mockito-junit-jupiter
108 | 2.23.0
109 | test
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dao/PaymentDAO.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dao;
2 |
3 | import com.politrons.domain.PaymentStateAggregateRoot;
4 | import com.politrons.infrastructure.events.PaymentAdded;
5 | import com.politrons.infrastructure.events.PaymentDeleted;
6 | import com.politrons.infrastructure.events.PaymentUpdated;
7 | import io.vavr.concurrent.Future;
8 | import io.vavr.control.Either;
9 |
10 | import javax.enterprise.context.ApplicationScoped;
11 | import java.util.List;
12 |
13 | @ApplicationScoped
14 | public interface PaymentDAO {
15 |
16 | Future> persistPaymentAddedEvent(PaymentAdded paymentAdded);
17 |
18 | Future> persistPaymentUpdatedEvent(PaymentUpdated paymentUpdated);
19 |
20 | Future> persistPaymentDeletedEvent(PaymentDeleted paymentAdded);
21 |
22 | Future> fetchPayment(String id);
23 |
24 | Future>> fetchAllPayments();
25 | }
26 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dao/impl/PaymentDAOImpl.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dao.impl;
2 |
3 | import com.datastax.driver.core.ResultSet;
4 | import com.datastax.driver.core.Row;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.politrons.domain.PaymentStateAggregateRoot;
7 | import com.politrons.infrastructure.CassandraConnector;
8 | import com.politrons.infrastructure.dao.PaymentDAO;
9 | import com.politrons.infrastructure.events.PaymentAdded;
10 | import com.politrons.infrastructure.events.PaymentDeleted;
11 | import com.politrons.infrastructure.events.PaymentEvent;
12 | import com.politrons.infrastructure.events.PaymentUpdated;
13 | import io.vavr.API;
14 | import io.vavr.concurrent.Future;
15 | import io.vavr.control.Either;
16 | import io.vavr.control.Try;
17 | import org.slf4j.Logger;
18 | import org.slf4j.LoggerFactory;
19 |
20 | import javax.enterprise.context.ApplicationScoped;
21 | import java.time.Instant;
22 | import java.util.List;
23 | import java.util.UUID;
24 | import java.util.stream.Collectors;
25 |
26 | import static io.vavr.API.*;
27 | import static io.vavr.Patterns.$Failure;
28 | import static io.vavr.Patterns.$Success;
29 |
30 | @ApplicationScoped
31 | public class PaymentDAOImpl implements PaymentDAO {
32 |
33 | private Logger logger = LoggerFactory.getLogger(PaymentDAOImpl.class);
34 |
35 | private ObjectMapper mapper = new ObjectMapper();
36 |
37 | @Override
38 | public Future> persistPaymentAddedEvent(PaymentAdded paymentAdded) {
39 | return upsertPayment(paymentAdded);
40 | }
41 |
42 | @Override
43 | public Future> persistPaymentUpdatedEvent(PaymentUpdated paymentUpdated) {
44 | return upsertPayment(paymentUpdated);
45 | }
46 |
47 | @Override
48 | public Future> persistPaymentDeletedEvent(PaymentDeleted paymentAdded) {
49 | return upsertPayment(paymentAdded);
50 | }
51 |
52 | /**
53 | * Method to find the Payment event in json format and transform into [PaymentStateAggregateRoot]
54 | *
55 | * @param id of the event
56 | * @return PaymentStateAggregateRoot
57 | */
58 | @Override
59 | public Future> fetchPayment(String id) {
60 | return Future.of(() -> CassandraConnector.fetchPayment(fetchPaymentByIdQuery(id)))
61 | .map(this::transformResultSetToPaymentAggregateRoot);
62 | }
63 |
64 | @Override
65 | public Future>> fetchAllPayments() {
66 | return Future.of(() -> CassandraConnector.fetchAllPayments(fetchAllPaymentsQuery()))
67 | .map(this::transformResultSetToPaymentAggregateRoots);
68 | }
69 |
70 | /**
71 | * Function to receive the event [PaymentEvent] and we persist into Cassandra row adding the UUID, timestamp and Event in json format.
72 | * We are doing Event sourcing, which means the internal id the of event it will remain intact for future rehydrate, and we will create a new UUID per
73 | * transaction inserted in Cassandra.
74 | *
75 | * @param paymentEvent Event to be transform into json and being persisted.
76 | * @return the id of the row
77 | */
78 | private Future> upsertPayment(PaymentEvent paymentEvent) {
79 | String uuid = createNewUUID();
80 | return Match(Try(() -> mapper.writeValueAsString(paymentEvent))
81 | .flatMap(event -> Try.of(() -> getAddPaymentQuery(uuid, getTimestampMillis(), event))
82 | .map(query -> Future.of(() -> CassandraConnector.addPayment(query))
83 | .map(maybeResultSet -> transformResultSetToId(maybeResultSet, uuid))))).of(
84 | Case($Success($()), future -> future),
85 | Case($Failure($()), throwable -> {
86 | logger.error("Error in add payment DAO mapping event to json. Caused by:" + throwable.getCause());
87 | return Future.of(() -> Left(throwable));
88 | }));
89 | }
90 |
91 | private Either> transformResultSetToPaymentAggregateRoots(Try maybeResultSet) {
92 | return Match(maybeResultSet).of(
93 | Case($Success($()), rs -> Right(getPaymentStateAggregateRoots(rs))),
94 | Case($Failure($()), throwable -> {
95 | logger.error("Error in fetch payment DAO transforming ResultSet. Caused by:" + throwable.getCause());
96 | return Left(throwable);
97 | }));
98 | }
99 |
100 | private List getPaymentStateAggregateRoots(ResultSet rs) {
101 | return rs.all().stream()
102 | .map(this::transformIntoPaymentStateAggregateRoot)
103 | .collect(Collectors.toList());
104 | }
105 |
106 | private PaymentStateAggregateRoot transformIntoPaymentStateAggregateRoot(Row row) {
107 | return Match(Try.of(() -> mapper.readValue(row.getString("event"), PaymentStateAggregateRoot.class))).of(
108 | Case($Success($()), paymentStateAggregateRoot -> paymentStateAggregateRoot),
109 | Case($Failure($()), throwable -> null));
110 | }
111 |
112 | /**
113 | * Function to transform from the Try monad of ResultSet into The id of the transaction.
114 | *
115 | * @param maybeResultSet monad with maybe the resultSet or a Throwable
116 | * @param id of the row
117 | * @return id of the event
118 | */
119 | private Either transformResultSetToId(Try maybeResultSet, String id) {
120 | return Match(maybeResultSet).of(
121 | Case($Success($()), resultSet -> Right(id)),
122 | Case($Failure($()), throwable -> {
123 | logger.error("Error in add payment DAO transforming ResultSet. Caused by:" + throwable.getCause());
124 | return Left(throwable);
125 | }));
126 | }
127 |
128 | /**
129 | * Function to transform from the Try monad of ResultSet into The PaymentStateAggregateRoot of the transaction.
130 | *
131 | * @param maybeResultSet monad with maybe the resultSet or a Throwable
132 | */
133 | private Either transformResultSetToPaymentAggregateRoot(Try maybeResultSet) {
134 | return Match(maybeResultSet).of(
135 | Case($Success($()), this::processResultSet),
136 | Case($Failure($()), throwable -> {
137 | logger.error("Error in fetch payment DAO transforming ResultSet. Caused by:" + throwable.getCause());
138 | return Left(throwable);
139 | }));
140 | }
141 |
142 | private Either processResultSet(ResultSet resultSet) {
143 | return Match(Try.of(() -> mapper.readValue(resultSet.one().getString("event"), PaymentStateAggregateRoot.class))).of(
144 | Case($Success($()), API::Right),
145 | Case($Failure($()), throwable -> {
146 | logger.error("Error in add payment DAO transforming ResultSet. Caused by:" + throwable.getCause());
147 | return Left(throwable);
148 | }));
149 | }
150 |
151 | private String getAddPaymentQuery(String id, String timeStampMillis, String event) {
152 | if (event == null || event.isEmpty() || event.equals("null")) throw new IllegalArgumentException();
153 | return "INSERT INTO " + "paymentsSchema.payment" +
154 | "(id, timestamp, event) " +
155 | "VALUES (" + id + ", '" +
156 | timeStampMillis + "', '" +
157 | event + "');";
158 | }
159 |
160 | private String fetchPaymentByIdQuery(String id) {
161 | return "SELECT * FROM paymentsSchema.payment WHERE id=" + id + " ";
162 | }
163 |
164 | private String fetchAllPaymentsQuery() {
165 | return "SELECT * FROM paymentsSchema.payment";
166 | }
167 |
168 |
169 | private String getTimestampMillis() {
170 | Instant instant = Instant.now();
171 | return String.valueOf(instant.toEpochMilli());
172 | }
173 |
174 | private String createNewUUID() {
175 | return UUID.randomUUID().toString();
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dto/BeneficiaryPartyDTO.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class BeneficiaryPartyDTO {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 |
20 | }
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dto/DebtorPartyDTO.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @Getter
9 | @Setter
10 | @NoArgsConstructor
11 | @AllArgsConstructor
12 | public class DebtorPartyDTO {
13 | private String accountName;
14 | private String accountNumber;
15 | private float accountType;
16 | private String address;
17 | private String bankId;
18 | private String name;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dto/PaymentInfoDTO.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentInfoDTO {
14 | private String amount;
15 | private String currency;
16 | private String paymentId;
17 | private String paymentPurpose;
18 | private String paymentType;
19 | private String processingDate;
20 | private String reference;
21 | private String schemePaymentSubType;
22 | private String schemePaymentType;
23 | DebtorPartyDTO debtorParty;
24 | SponsorPartyDTO sponsorParty;
25 | BeneficiaryPartyDTO beneficiaryParty;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/dto/SponsorPartyDTO.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dto;
2 |
3 |
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class SponsorPartyDTO {
14 | private String accountNumber;
15 | private String bankId;
16 | private String bankIdCode;
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/events/PaymentAdded.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.events;
2 |
3 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentAdded implements PaymentEvent{
14 | String id;
15 | String type;
16 | float version;
17 | PaymentInfoDTO paymentInfo;
18 | }
19 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/events/PaymentDeleted.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.events;
2 |
3 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentDeleted implements PaymentEvent{
14 | String id;
15 | String type;
16 | float version;
17 | PaymentInfoDTO paymentInfo;
18 | }
19 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/events/PaymentEvent.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.events;
2 |
3 | public interface PaymentEvent {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/events/PaymentUpdated.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.events;
2 |
3 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | @Getter
10 | @Setter
11 | @NoArgsConstructor
12 | @AllArgsConstructor
13 | public class PaymentUpdated implements PaymentEvent{
14 | String id;
15 | String type;
16 | float version;
17 | PaymentInfoDTO paymentInfo;
18 | }
19 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/repository/PaymentRepository.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.repository;
2 |
3 | import com.politrons.domain.PaymentStateAggregateRoot;
4 | import io.vavr.concurrent.Future;
5 | import io.vavr.control.Either;
6 |
7 | import javax.enterprise.context.ApplicationScoped;
8 |
9 | @ApplicationScoped
10 | public interface PaymentRepository {
11 |
12 | Future> addPayment(PaymentStateAggregateRoot paymentStateAggregateRoot);
13 |
14 | Future> updatePayment(PaymentStateAggregateRoot paymentStateAggregateRoot);
15 |
16 | Future> deletePayment(PaymentStateAggregateRoot paymentStateAggregateRoot);
17 |
18 | Future> fetchPayment(String id);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/infrastructure/src/main/java/com/politrons/infrastructure/repository/impl/PaymentRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.repository.impl;
2 |
3 | import com.politrons.domain.PaymentStateAggregateRoot;
4 | import com.politrons.infrastructure.events.PaymentDeleted;
5 | import com.politrons.infrastructure.events.PaymentEvent;
6 | import com.politrons.infrastructure.events.PaymentUpdated;
7 | import com.politrons.infrastructure.repository.PaymentRepository;
8 | import com.politrons.infrastructure.dao.PaymentDAO;
9 | import com.politrons.infrastructure.events.PaymentAdded;
10 | import io.vavr.concurrent.Future;
11 | import io.vavr.control.Either;
12 | import lombok.NoArgsConstructor;
13 | import ma.glasnost.orika.MapperFacade;
14 | import ma.glasnost.orika.MapperFactory;
15 | import ma.glasnost.orika.impl.DefaultMapperFactory;
16 |
17 | import javax.enterprise.context.ApplicationScoped;
18 | import javax.inject.Inject;
19 |
20 | @NoArgsConstructor
21 | @ApplicationScoped
22 | public class PaymentRepositoryImpl implements PaymentRepository {
23 |
24 | @Inject
25 | PaymentDAO paymentDAO;
26 |
27 | private MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
28 |
29 | PaymentRepositoryImpl(PaymentDAO paymentDAO) {
30 | this.paymentDAO = paymentDAO;
31 | }
32 |
33 | /**
34 | * Method responsible for the transformation from the AggregateRoot of the domain layer
35 | * into the PaymentAdded Event to be persisted.
36 | *
37 | * @param paymentStateAggregateRoot domain model to be transform into event.
38 | * @return The id of the transaction for futures fetch.
39 | */
40 | @Override
41 | public Future> addPayment(PaymentStateAggregateRoot paymentStateAggregateRoot) {
42 | PaymentAdded paymentAdded = transformAggregateRootIntoEvent(paymentStateAggregateRoot, PaymentAdded.class);
43 | return paymentDAO.persistPaymentAddedEvent(paymentAdded);
44 | }
45 |
46 | /**
47 | * Method responsible for the transformation from the AggregateRoot of the domain layer
48 | * into the PaymentUpdated Event to be persisted.
49 | *
50 | * @param paymentStateAggregateRoot domain model to be transform into event.
51 | * @return The id of the transaction for futures fetch.
52 | */
53 | @Override
54 | public Future> updatePayment(PaymentStateAggregateRoot paymentStateAggregateRoot) {
55 | PaymentUpdated paymentUpdated = transformAggregateRootIntoEvent(paymentStateAggregateRoot, PaymentUpdated.class);
56 | return paymentDAO.persistPaymentUpdatedEvent(paymentUpdated);
57 | }
58 |
59 | /**
60 | * Method responsible for the transformation from the AggregateRoot of the domain layer
61 | * into the PaymentDeleted Event to be persisted.
62 | *
63 | * @param paymentStateAggregateRoot domain model to be transform into event.
64 | * @return The id of the transaction for futures fetch.
65 | */
66 | @Override
67 | public Future> deletePayment(PaymentStateAggregateRoot paymentStateAggregateRoot) {
68 | PaymentDeleted paymentDeleted = transformAggregateRootIntoEvent(paymentStateAggregateRoot, PaymentDeleted.class);
69 | return paymentDAO.persistPaymentDeletedEvent(paymentDeleted);
70 | }
71 |
72 | /**
73 | * Proxy method to be used from handler by delete Payment to get the payment first from the Database.
74 | *
75 | * @param id of the eventId
76 | * @return The PaymentStateAggregateRoot to change state as deleted and create a new Event.
77 | */
78 | @Override
79 | public Future> fetchPayment(String id) {
80 | return paymentDAO.fetchPayment(id);
81 | }
82 |
83 | private T transformAggregateRootIntoEvent(PaymentStateAggregateRoot paymentStateAggregateRoot,
84 | Class extends PaymentEvent> paymentEventClass) {
85 | mapperFactory.classMap(PaymentStateAggregateRoot.class, paymentEventClass);
86 | MapperFacade mapper = mapperFactory.getMapperFacade();
87 | return (T) mapper.map(paymentStateAggregateRoot, paymentEventClass);
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/infrastructure/src/main/resources/META-INF/beans.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/politrons/PaymentAPI/2e12a671000d26cf34fe4afb3f96b99ffff7ce65/infrastructure/src/main/resources/META-INF/beans.xml
--------------------------------------------------------------------------------
/infrastructure/src/main/scala/com/politrons/infrastructure/CassandraConnector.scala:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure
2 |
3 | import com.datastax.driver.core._
4 | import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory
5 | import com.github.nosan.embedded.cassandra.{Cassandra, Settings}
6 | import io.vavr.control.Try
7 |
8 | /**
9 | * Simple cassandra client, following the datastax documentation
10 | * (http://www.datastax.com/documentation/developer/java-driver/2.0/java-driver/quick_start/qsSimpleClientCreate_t.html).
11 | */
12 | object CassandraConnector {
13 |
14 | lazy val defaultTable =
15 | s"""CREATE TABLE IF NOT EXISTS paymentsSchema.payment (
16 | id uuid,
17 | timestamp varchar,
18 | event varchar,
19 | PRIMARY KEY (id )
20 | );"""
21 |
22 | var session: Session = _
23 | private var cluster: Cluster = _
24 | private var cassandra: Cassandra = _
25 |
26 | def isStarted(): Boolean = session != null && !session.isClosed
27 |
28 | def start(): Unit = {
29 | if(!isStarted()){
30 | val cassandraFactory = new LocalCassandraFactory
31 | cassandra = cassandraFactory.create()
32 | cassandra.start()
33 | initCassandra()
34 | }
35 | }
36 |
37 | def stop() {
38 | session.close()
39 | cluster.close()
40 | cassandra.stop()
41 | }
42 |
43 | def addPayment(query: String): Try[ResultSet] = executeQuery(query)
44 |
45 | def fetchPayment(query: String): Try[ResultSet] = executeQuery(query)
46 |
47 | def fetchAllPayments(query: String): Try[ResultSet] = executeQuery(query)
48 |
49 | /**
50 | * Init the session/cluster to cassandra nd create the keyspace and table in case does not exist.
51 | */
52 | def initCassandra(): Unit = {
53 | initCluster(cassandra.getSettings)
54 | cleanCassandra()
55 | createSchema()
56 | createTable()
57 | }
58 |
59 | private def initCluster(settings: Settings): Unit = {
60 | val socketOptions = new SocketOptions
61 | socketOptions.setConnectTimeoutMillis(30000)
62 | socketOptions.setReadTimeoutMillis(30000)
63 | cluster = Cluster.builder
64 | .addContactPoints(settings.getAddress)
65 | .withPort(settings.getPort)
66 | .withSocketOptions(socketOptions)
67 | .withoutJMXReporting
68 | .withoutMetrics
69 | .build
70 | session = cluster.connect
71 | }
72 |
73 | private def createSchema(keySpace: String = "paymentsSchema"): Unit = {
74 | session.execute(s"CREATE KEYSPACE IF NOT EXISTS $keySpace WITH replication = {'class':'SimpleStrategy', 'replication_factor':1};")
75 | println(s"Keyspace $keySpace created if not exists")
76 | }
77 |
78 | private def createTable(script: String = defaultTable): Unit = {
79 | session.execute(script)
80 | println(s"Table created if not exist")
81 | }
82 |
83 | /**
84 | * Clean the table data from the test
85 | */
86 | private def cleanCassandra(keySpace: String = "paymentsSchema", table: String = "payment"): Unit = {
87 | executeQuery(s"""truncate $keySpace.$table""")
88 | }
89 |
90 | private def executeQuery(query: String): Try[ResultSet] = {
91 | Try.of(() => session.execute(query))
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/infrastructure/src/test/java/com/politrons/infrastructure/CassandraMock.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure;
2 |
3 | import com.datastax.driver.core.Cluster;
4 | import com.datastax.driver.core.Session;
5 | import com.datastax.driver.core.SocketOptions;
6 | import com.github.nosan.embedded.cassandra.Cassandra;
7 | import com.github.nosan.embedded.cassandra.Settings;
8 | import com.github.nosan.embedded.cassandra.cql.CqlScript;
9 | import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;
10 |
11 | import java.util.List;
12 |
13 | public class CassandraMock {
14 |
15 | static private Cassandra cassandra;
16 |
17 | public static void start() {
18 | LocalCassandraFactory cassandraFactory = new LocalCassandraFactory();
19 | cassandra = cassandraFactory.create();
20 | cassandra.start();
21 | Settings settings = cassandra.getSettings();
22 | executeScripts(settings);
23 | }
24 |
25 | public static void stopCassandra(){
26 | cassandra.stop();
27 | }
28 |
29 | private static void executeScripts(Settings settings) {
30 | SocketOptions socketOptions = new SocketOptions();
31 | socketOptions.setConnectTimeoutMillis(30000);
32 | socketOptions.setReadTimeoutMillis(30000);
33 | try (Cluster cluster = Cluster.builder().addContactPoints(settings.getAddress())
34 | .withPort(settings.getPort()).withSocketOptions(socketOptions)
35 | .withoutJMXReporting().withoutMetrics()
36 | .build()) {
37 | Session session = cluster.connect();
38 | // List statements = CqlScript.classpath("schema.cql").getStatements();
39 | // statements.forEach(session::execute);
40 | }
41 | }
42 |
43 |
44 | }
--------------------------------------------------------------------------------
/infrastructure/src/test/java/com/politrons/infrastructure/dao/impl/PaymentDAOBackendDownTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dao.impl;
2 |
3 | import com.politrons.infrastructure.events.PaymentAdded;
4 | import io.vavr.concurrent.Future;
5 | import io.vavr.control.Either;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import java.util.UUID;
9 |
10 | import static org.junit.jupiter.api.Assertions.assertEquals;
11 | import static org.junit.jupiter.api.Assertions.assertTrue;
12 |
13 | public class PaymentDAOBackendDownTest extends PaymentDAOUtilsTest{
14 |
15 | private PaymentDAOImpl paymentDAO = new PaymentDAOImpl();
16 |
17 | @Test
18 | void addPaymentEvenWithDatabaseDown() {
19 | Future> eithers = paymentDAO.persistPaymentAddedEvent(getPaymentAddedEvent());
20 | assertTrue(eithers.get().isLeft());
21 | }
22 |
23 | protected PaymentAdded getPaymentAddedEvent() {
24 | return new PaymentAdded(UUID.randomUUID().toString(), "payment", 0, getPaymentInfoDTO());
25 | }
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/infrastructure/src/test/java/com/politrons/infrastructure/dao/impl/PaymentDAOTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dao.impl;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.politrons.domain.PaymentStateAggregateRoot;
6 | import com.politrons.infrastructure.CassandraConnector;
7 | import com.politrons.infrastructure.events.PaymentAdded;
8 | import io.vavr.concurrent.Future;
9 | import io.vavr.control.Either;
10 | import org.junit.jupiter.api.AfterAll;
11 | import org.junit.jupiter.api.BeforeAll;
12 | import org.junit.jupiter.api.Test;
13 |
14 | import java.util.List;
15 | import java.util.UUID;
16 |
17 | import static org.junit.jupiter.api.Assertions.*;
18 |
19 | public class PaymentDAOTest extends PaymentDAOUtilsTest {
20 |
21 | private PaymentDAOImpl paymentDAO = new PaymentDAOImpl();
22 |
23 | private ObjectMapper mapper = new ObjectMapper();
24 |
25 |
26 | @BeforeAll
27 | static void init() {
28 | CassandraConnector.start();
29 | }
30 |
31 | //####################//
32 | // Add payment //
33 | //####################//
34 | @Test
35 | void paymentAddedEvent() {
36 | Future> eithers = paymentDAO.persistPaymentAddedEvent(getPaymentAddedEvent());
37 | assertTrue(eithers.get().isRight());
38 | assertFalse(eithers.get().right().get().isEmpty());
39 | }
40 |
41 | @Test
42 | void paymentAddedEventWithWrongJson() {
43 | Future> eithers = paymentDAO.persistPaymentAddedEvent(null);
44 | assertTrue(eithers.get().isLeft());
45 | }
46 |
47 | //####################//
48 | // Update payment //
49 | //####################//
50 | @Test
51 | void paymentUpdatedEvent() {
52 | Future> eithers = paymentDAO.persistPaymentUpdatedEvent(getPaymentUpdatedEvent());
53 | assertTrue(eithers.get().isRight());
54 | assertFalse(eithers.get().right().get().isEmpty());
55 | }
56 |
57 | @Test
58 | void paymentUpdatedEventWithWrongJson() {
59 | Future> eithers = paymentDAO.persistPaymentUpdatedEvent(null);
60 | assertTrue(eithers.get().isLeft());
61 | }
62 |
63 | //####################//
64 | // Delete payment //
65 | //####################//
66 | @Test
67 | void paymentDeletedEvent() {
68 | Future> eithers = paymentDAO.persistPaymentDeletedEvent(getPaymentDeletedEvent());
69 | assertTrue(eithers.get().isRight());
70 | assertFalse(eithers.get().right().get().isEmpty());
71 | }
72 |
73 | @Test
74 | void paymentDeletedEventWithWrongJson() {
75 | Future> eithers = paymentDAO.persistPaymentDeletedEvent(null);
76 | assertTrue(eithers.get().isLeft());
77 | }
78 |
79 | //####################//
80 | // Fetch payment //
81 | //####################//
82 | @Test
83 | void fetchPayment() throws JsonProcessingException {
84 | String uuid = addPayment();
85 | Future> eithers = paymentDAO.fetchPayment(uuid);
86 | assertTrue(eithers.get().isRight());
87 | assertEquals(eithers.get().right().get().getId(), uuid);
88 | }
89 |
90 | @Test
91 | void fetchPaymentWithWrongId() {
92 | Future> eithers = paymentDAO.fetchPayment("foo");
93 | assertTrue(eithers.get().isLeft());
94 | }
95 |
96 | @Test
97 | void fetchAllPayment() throws JsonProcessingException {
98 | addPayment();
99 | addPayment();
100 | Future>> eithers = paymentDAO.fetchAllPayments();
101 | assertTrue(eithers.get().isRight());
102 | assertEquals(eithers.get().right().get().size(), 2);
103 | }
104 |
105 | String addPayment() throws JsonProcessingException {
106 | String uuid = UUID.randomUUID().toString();
107 | PaymentAdded paymentAddedEvent = getPaymentAddedEvent();
108 | paymentAddedEvent.setId(uuid);
109 | String event = mapper.writeValueAsString(paymentAddedEvent);
110 | CassandraConnector.addPayment(getAddPaymentQuery(paymentAddedEvent.getId(), "12345", event));
111 | return uuid;
112 | }
113 |
114 | @AfterAll
115 | static void close() {
116 | CassandraConnector.stop();
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/infrastructure/src/test/java/com/politrons/infrastructure/dao/impl/PaymentDAOUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.dao.impl;
2 |
3 | import com.politrons.infrastructure.dto.BeneficiaryPartyDTO;
4 | import com.politrons.infrastructure.dto.DebtorPartyDTO;
5 | import com.politrons.infrastructure.dto.PaymentInfoDTO;
6 | import com.politrons.infrastructure.dto.SponsorPartyDTO;
7 | import com.politrons.infrastructure.events.PaymentAdded;
8 | import com.politrons.infrastructure.events.PaymentDeleted;
9 | import com.politrons.infrastructure.events.PaymentUpdated;
10 |
11 | import java.util.UUID;
12 |
13 | public class PaymentDAOUtilsTest {
14 |
15 |
16 | String getAddPaymentQuery(String id, String timeStampMillis, String event) {
17 | return "INSERT INTO " + "paymentsSchema.payment" +
18 | "(id, timestamp, event) " +
19 | "VALUES (" + id + ", '" +
20 | timeStampMillis + "', '" +
21 | event + "');";
22 | }
23 |
24 | protected PaymentAdded getPaymentAddedEvent() {
25 | return new PaymentAdded(UUID.randomUUID().toString(), "created", 0, getPaymentInfoDTO());
26 | }
27 |
28 | protected PaymentUpdated getPaymentUpdatedEvent() {
29 | return new PaymentUpdated(UUID.randomUUID().toString(), "changed", 0, getPaymentInfoDTO());
30 | }
31 |
32 | protected PaymentDeleted getPaymentDeletedEvent() {
33 | return new PaymentDeleted(UUID.randomUUID().toString(), "deleted", 0, getPaymentInfoDTO());
34 | }
35 |
36 | PaymentInfoDTO getPaymentInfoDTO() {
37 | DebtorPartyDTO debtorParty = getDebtorParty();
38 | BeneficiaryPartyDTO beneficiaryParty = getBeneficiaryParty();
39 | SponsorPartyDTO sponsorParty = getSponsorParty();
40 | return new PaymentInfoDTO("amount", "currency",
41 | "paymentId",
42 | "paymentPurpose", "paymentType",
43 | "processingDate", "reference", "schemePaymentSubType",
44 | "schemePaymentType", debtorParty, sponsorParty, beneficiaryParty);
45 | }
46 |
47 | private SponsorPartyDTO getSponsorParty() {
48 | return new SponsorPartyDTO("accountName",
49 | "bankId", "bankCode");
50 | }
51 |
52 | private BeneficiaryPartyDTO getBeneficiaryParty() {
53 | return new BeneficiaryPartyDTO("accountName",
54 | "accountNumber", 0, "address", "bankId", "name");
55 | }
56 |
57 | private DebtorPartyDTO getDebtorParty() {
58 | return new DebtorPartyDTO("accountName", "accountNumber",
59 | 0, "address", "bankId", "name");
60 | }
61 |
62 |
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/infrastructure/src/test/java/com/politrons/infrastructure/repository/impl/PaymentRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package com.politrons.infrastructure.repository.impl;
2 |
3 | import com.politrons.domain.PaymentStateAggregateRoot;
4 | import com.politrons.domain.entities.BeneficiaryParty;
5 | import com.politrons.domain.entities.DebtorParty;
6 | import com.politrons.domain.entities.PaymentInfo;
7 | import com.politrons.domain.entities.SponsorParty;
8 | import com.politrons.infrastructure.dao.PaymentDAO;
9 | import com.politrons.infrastructure.events.PaymentAdded;
10 | import com.politrons.infrastructure.events.PaymentDeleted;
11 | import com.politrons.infrastructure.events.PaymentUpdated;
12 | import com.politrons.infrastructure.repository.PaymentRepository;
13 | import io.vavr.concurrent.Future;
14 | import io.vavr.control.Either;
15 | import org.junit.jupiter.api.BeforeEach;
16 | import org.junit.jupiter.api.Test;
17 | import org.junit.jupiter.api.extension.ExtendWith;
18 | import org.mockito.Mock;
19 | import org.mockito.junit.jupiter.MockitoExtension;
20 |
21 | import static io.vavr.API.Right;
22 | import static org.junit.jupiter.api.Assertions.assertFalse;
23 | import static org.junit.jupiter.api.Assertions.assertTrue;
24 | import static org.mockito.ArgumentMatchers.any;
25 | import static org.mockito.Mockito.when;
26 |
27 | @ExtendWith(MockitoExtension.class)
28 | public class PaymentRepositoryTest {
29 |
30 | @Mock
31 | PaymentDAO paymentDAO;
32 |
33 | private PaymentRepository paymentRepository;
34 |
35 | @BeforeEach
36 | void setup() {
37 | paymentRepository = new PaymentRepositoryImpl(paymentDAO);
38 | }
39 |
40 | @Test
41 | void addPaymentEvent() {
42 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("id", "payment", 0, getPaymentInfo());
43 | when(paymentDAO.persistPaymentAddedEvent(any(PaymentAdded.class))).thenReturn(Future.of(() -> Right("1981")));
44 | Future> eithers = paymentRepository.addPayment(paymentStateAggregateRoot);
45 | assertTrue(eithers.get().isRight());
46 | assertFalse(eithers.get().right().get().isEmpty());
47 | }
48 |
49 | @Test
50 | void updatePaymentEvent() {
51 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("id", "payment", 0, getPaymentInfo());
52 | when(paymentDAO.persistPaymentUpdatedEvent(any(PaymentUpdated.class))).thenReturn(Future.of(() -> Right("1981")));
53 | Future> eithers = paymentRepository.updatePayment(paymentStateAggregateRoot);
54 | assertTrue(eithers.get().isRight());
55 | assertFalse(eithers.get().right().get().isEmpty());
56 | }
57 |
58 | @Test
59 | void deletePaymentEvent() {
60 | PaymentStateAggregateRoot paymentStateAggregateRoot = new PaymentStateAggregateRoot("id", "payment", 0, getPaymentInfo());
61 | when(paymentDAO.persistPaymentDeletedEvent(any(PaymentDeleted.class))).thenReturn(Future.of(() -> Right("1981")));
62 | Future> eithers = paymentRepository.deletePayment(paymentStateAggregateRoot);
63 | assertTrue(eithers.get().isRight());
64 | assertFalse(eithers.get().right().get().isEmpty());
65 | }
66 |
67 | private PaymentInfo getPaymentInfo() {
68 | DebtorParty debtorParty = getDebtorParty();
69 | BeneficiaryParty beneficiaryParty = getBeneficiaryParty();
70 | SponsorParty sponsorParty = getSponsorParty();
71 | return new PaymentInfo("amount", "currency",
72 | "paymentId",
73 | "paymentPurpose", "paymentType",
74 | "processingDate", "reference", "schemePaymentSubType",
75 | "schemePaymentType", debtorParty, sponsorParty, beneficiaryParty);
76 | }
77 |
78 | private SponsorParty getSponsorParty() {
79 | return new SponsorParty("accountName",
80 | "bankId", "bankCode");
81 | }
82 |
83 | private BeneficiaryParty getBeneficiaryParty() {
84 | return new BeneficiaryParty("accountName",
85 | "accountNumber", 0, "address", "bankId", "name");
86 | }
87 |
88 | private DebtorParty getDebtorParty() {
89 | return new DebtorParty("accountName", "accountNumber",
90 | 0, "address", "bankId", "name");
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/paymentAPI.iml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 | org.acme
7 | paymentAPI
8 | pom
9 | 1.0-SNAPSHOT
10 |
11 | application
12 | infrastructure
13 | domain
14 |
15 |
16 | 2.22.0
17 | 0.15.0
18 | UTF-8
19 | 1.8
20 | 1.8
21 |
22 |
23 |
24 |
25 | io.quarkus
26 | quarkus-bom
27 | ${quarkus.version}
28 | pom
29 | import
30 |
31 |
32 |
33 |
34 |
35 |
36 | io.quarkus
37 | quarkus-resteasy
38 |
39 |
40 | io.quarkus
41 | quarkus-vertx
42 |
43 |
44 | io.quarkus
45 | quarkus-smallrye-health
46 |
47 |
48 | io.quarkus
49 | quarkus-undertow-websockets
50 |
51 |
52 | io.quarkus
53 | quarkus-junit5
54 | test
55 |
56 |
57 | io.rest-assured
58 | rest-assured
59 | test
60 |
61 |
62 | io.quarkus
63 | quarkus-smallrye-openapi
64 |
65 |
66 |
67 |
68 | org.projectlombok
69 | lombok
70 | 1.18.6
71 |
72 |
73 |
74 | org.slf4j
75 | slf4j-api
76 | 1.7.26
77 |
78 |
79 |
80 |
81 | io.vavr
82 | vavr
83 | 0.10.0
84 |
85 |
86 |
87 | io.gatling
88 | gatling-app
89 | 2.3.1
90 |
91 |
92 | io.gatling.highcharts
93 | gatling-charts-highcharts
94 | 2.3.1
95 |
96 |
97 | io.netty
98 | *
99 |
100 |
101 |
102 |
103 | org.scala-lang
104 | scala-library
105 | 2.12.4
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | org.scalatest
114 | scalatest_2.12
115 | 3.0.1
116 | test
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | src/test/scala
126 |
127 |
128 | src/test/resources
129 |
130 | **/*
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | io.quarkus
139 | quarkus-maven-plugin
140 | ${quarkus.version}
141 |
142 |
143 |
144 | build
145 |
146 |
147 |
148 |
149 |
150 | maven-surefire-plugin
151 | ${surefire-plugin.version}
152 |
153 |
154 | org.jboss.logmanager.LogManager
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | native
163 |
164 |
165 | native
166 |
167 |
168 |
169 |
170 |
171 | io.quarkus
172 | quarkus-maven-plugin
173 | ${quarkus.version}
174 |
175 |
176 |
177 | native-image
178 |
179 |
180 | true
181 |
182 |
183 |
184 |
185 |
186 | maven-failsafe-plugin
187 | ${surefire-plugin.version}
188 |
189 |
190 |
191 | integration-test
192 | verify
193 |
194 |
195 |
196 |
197 | ${project.build.directory}/${project.build.finalName}-runner
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 | net.alchim31.maven
208 | scala-maven-plugin
209 |
210 |
211 | scala-compile-first
212 | process-resources
213 |
214 | add-source
215 | compile
216 |
217 |
218 |
219 | scala-test-compile
220 | process-test-resources
221 |
222 | testCompile
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 | org.scalatest
231 | scalatest-maven-plugin
232 | 1.0
233 |
234 | ${project.build.directory}/surefire-reports
235 | .
236 | WDF TestSuite.txt
237 | -Dcrypto.uuid.enabled=false
238 |
239 |
240 |
241 |
242 | test
243 | test
244 |
245 | test
246 |
247 |
248 |
249 |
250 |
251 |
252 | org.apache.maven.plugins
253 | maven-compiler-plugin
254 |
255 |
256 | compile
257 |
258 | compile
259 |
260 |
261 |
262 |
263 |
264 | io.gatling
265 | gatling-maven-plugin
266 | 2.2.2
267 |
268 | true
269 | ${project.basedir}/src/test/resources
270 | ${project.basedir}/src/test/resources/data
271 | ${project.basedir}/target/gatling/results
272 | ${project.basedir}/src/test/resources/bodies
273 |
274 | true
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
--------------------------------------------------------------------------------