>() {
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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Architecture
3 |
4 | For the design of PaymentAPI I used DDD + CQRS + Event Sourcing, I will explain a little bit why the use of these three architectures patters.
5 |
6 | ### DDD
7 |
8 | Domain Driven Design it's a design pattern for dealing with highly complex domains that is based on making the domain itself the main focus of the project.
9 |
10 | Also it helps to decouple the different layers of your program(Application, Domain, Infrastructure) making it more extensible and easy to port each of the layers into another program if it's needed.
11 |
12 | In PaymentAPI I implemented three modules described like:
13 |
14 | * **Application**: Layer which normally is the entry point of your app. It has dependency with domain and infrastructure modules.
15 | * **Domain**: Layer where you implement the business logic of your application. It does not have any dependency.
16 | * **Infrastructure**: Layer where you implement the communication with the backends (Database, services, Brokers). It has dependency with domain.
17 |
18 | 
19 |
20 | ### CQRS
21 |
22 | Command Query Responsibility Segregation it's another Design pattern, mostly divulged by **Greg Young** which segregate the model of Commands and Queries
23 | in your architecture to do Writes and Reads separated. It can give you the chance to make the Queries more efficient since normally in a Cluster the 90% of the traffic
24 | is for Queries.
25 |
26 | In PaymentAPI I implemented Handler for Commands and Service to deal with Queries.
27 |
28 | * **Handler**: Receives commands and transforms the Command into Domain model, to pass the responsibility to the domain, and finally into the infrastructure.
29 | * **Service**: Receives Queries and invokes directly to the DAO in the infrastructure layer.
30 |
31 | ### Event Sourcing
32 |
33 | Event sourcing is a design pattern where you work with Events. The main idea of Event sourcing is to keep and persist the state of your program, without mutating previous states.
34 |
35 | That means that with Event sourcing I'm not deleting any data in the system, giving us the possibility to have an historical about the different state of one Payment.
36 |
37 | In PaymentAPI, Event sourcing together with CQRS, allow us persist events in order to keep state and have historical data.
38 |
39 | * **PaymentAdded**: Event that keep the state of the creation of a payment.
40 | * **PaymentUpdated**: Event that keep the state of the change of a payment.
41 | * **PaymentDeleted**: Event that keep the state of the deletion of a payment.
42 |
43 | ### API
44 |
45 | The design of the API is based on Restful design, so for Queries I used **GET**, for create **POST**, update **PUT** and delete **DELETE**
46 |
47 | Since the root of the endpoints is `/v1/payment` many of the endpoints do not require any extra word, only arguments as `Query param`
48 |
49 | ```.java
50 | @Path("/v1/payment")
51 | @Produces(MediaType.APPLICATION_JSON)
52 | @Consumes(MediaType.APPLICATION_JSON)
53 | public class PaymentResource {
54 |
55 | @GET
56 | @Path("/all")
57 | public CompletionStage> fetchAllPayment()
58 |
59 | @GET
60 | @Path("/{paymentId}")
61 | public CompletionStage> fetchPaymentById(@PathParam("paymentId") String id)
62 |
63 | @POST
64 | @Path("/")
65 | public CompletionStage> addPayment(AddPaymentCommand addPaymentCommand)
66 |
67 | @PUT
68 | @Path("/{paymentId}")
69 | public CompletionStage> updatePayment(@PathParam("paymentId") String paymentId,
70 | UpdatePaymentCommand updatePaymentCommand)
71 | @DELETE
72 | @Path("/{paymentId}")
73 | public CompletionStage> deletePayment(@PathParam("paymentId") String paymentId)
74 | }
75 | ```
76 | The request body for Create and Update Payment it has this json format
77 |
78 | ```
79 | {
80 | "amount": "amount",
81 | "currency": "currency",
82 | "paymentId": "paymentId",
83 | "debtorParty": {
84 | "accountName": "accountName",
85 | "accountNumber": "accountNumber",
86 | "accountType": 0.0,
87 | "address": "address",
88 | "bankId": "bankId",
89 | "name": "name"
90 | },
91 | "sponsorParty": {
92 | "accountNumber": "accountName",
93 | "bankId": "bankId",
94 | "bankIdCode": "bankCode"
95 | },
96 | "beneficiaryParty": {
97 | "accountName": "accountName",
98 | "accountNumber": "accountNumber",
99 | "accountType": 0.0,
100 | "address": "address",
101 | "bankId": "bankId",
102 | "name": "name"
103 | },
104 | "paymentPurpose": "paymentPurpose",
105 | "paymentType": "paymentType",
106 | "processingDate": "processingDate",
107 | "reference": "reference",
108 | "schemePaymentSubType": "schemePaymentSubType",
109 | "schemePaymentType": "schemePaymentType"
110 | }
111 | ```
112 |
113 | And the json response format for fetch payment has this one.
114 |
115 | ```
116 | {
117 | "id": "id",
118 | "type": "created",
119 | "version": 1.0,
120 | "paymentInfo": {
121 | "amount": "amount",
122 | "currency": "currency",
123 | "paymentId": "paymentId",
124 | "paymentPurpose": "paymentPurpose",
125 | "paymentType": "paymentType",
126 | "processingDate": "processingDate",
127 | "reference": "reference",
128 | "schemePaymentSubType": "schemePaymentSubType",
129 | "schemePaymentType": "schemePaymentType",
130 | "debtorParty": {
131 | "accountName": "accountName",
132 | "accountNumber": "accountNumber",
133 | "accountType": 0.0,
134 | "address": "address",
135 | "bankId": "bankId",
136 | "name": "name"
137 | },
138 | "sponsorParty": {
139 | "accountNumber": "accountName",
140 | "bankId": "bankId",
141 | "bankIdCode": "bankCode"
142 | },
143 | "beneficiaryParty": {
144 | "accountName": "accountName",
145 | "accountNumber": "accountNumber",
146 | "accountType": 0.0,
147 | "address": "address",
148 | "bankId": "bankId",
149 | "name": "name"
150 | }
151 | }
152 | }
153 | ```
154 |
155 | ## Testing
156 |
157 | 
158 |
159 | **To go fast you have to go well** this quote of Robert C. Martin express perfectly what TDD and BDD is. You should think first in all corner cases of your program, and then implement
160 | one by one committing every scenario to have a quick feedback about your program.
161 |
162 | In our application I invested around 70% of the time implementing the test framework, the type of testing implemented are described below.
163 |
164 | * **Unit**: I used JUnit5 together with Mockito to Mock external resources of your class.
165 | * **Integration**: I used Quarkus server, which include a very nice Test framework to do BDD and run the application and test all layers of your application.
166 | Just to be clear, the IT test are just a proof that our Unit test are well designed and the Mock behaves as I expect. None IT test should ever fail. And if it does,
167 | you have to reproduce it in Unit test.
168 | * **Performance**: Performance/Stress test are meant to be used to detect possible concurrency issue in your application,
169 | and also to have a control of the latency of your endpoints.
170 | Quarkus seems like it does not work with **Gatling** so I just use **@RepeatedTest** of JUnit5.
171 | So, no control of latency of endpoints available.
172 | * **Volume**: Volume test are meant to be used to have a load of traffic for a long period of time, to detect
173 | some possible memory leaks in your application that it might provoke that your application get
174 | out of memory and die.
175 | Quarkus seems like it does not work with **Gatling** so I just use **@RepeatedTest** of JUnit5.
176 |
177 | ## Technology Stack
178 |
179 | As Http Server I used **Quarkus**, a new Serverless framework implemented by RedHat team, as a new Power Vertx framework. Indeed a good choice to create Reactive systems.
180 |
181 | For the API I used **JAX-RS** standard.
182 |
183 | In order to have a reactive system, functional programing it's a powerful tool. Although Java is functional since Java 8 it's not Scala. **Vavr** is good library for functional
184 | programing that makes you feel like you're in Scala realm again.
185 |
186 | To make transformation of models between layer doing DDD, I used **Orika**.
187 |
188 | Finally as Connector and Database I chose Cassandra since it's a good choice for Event sourcing.
189 |
190 | * **Application**: Quarkus, JAX-RS, Vavr, Mockito
191 |
192 | * **Domain**: Vavr, Orika
193 |
194 | * **Infrastructure**: Orika, Vavr, Orika, Cassandra, Mockito.
195 |
196 |
197 | ## Use
198 |
199 | To run the Unit and Integration test, just go into the root program and run the command:
200 |
201 | ```
202 | mvn clean install
203 | ```
204 |
205 | Once that you have your project tested, you just need to run the application. Go to the application module and run the command:
206 |
207 | ```
208 | ./mvnw compile quarkus:dev
209 | ```
210 |
211 | Then you will have to run the Cassandra embedded that the project provide.
212 |
213 | ```
214 | CassandraConnector.start()
215 | ```
216 |
217 |
218 | ## Health check
219 |
220 | PaymentAPI provide a health check endpoint to know the state of the service and backends.
221 |
222 | ```
223 | http://localhost:8080/health
224 | ```
225 |
226 | ##### Response
227 |
228 | ```
229 | {
230 | "outcome": "UP",
231 | "checks": [
232 | {
233 | "name": "Payment API health check",
234 | "state": "UP",
235 | "data": {
236 | "Cassandra database": "running"
237 | }
238 | }
239 | ]
240 | }
241 | ```
242 |
--------------------------------------------------------------------------------
/application/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Mingw, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | fi
118 |
119 | if [ -z "$JAVA_HOME" ]; then
120 | javaExecutable="`which javac`"
121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
122 | # readlink(1) is not available as standard on Solaris 10.
123 | readLink=`which readlink`
124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
125 | if $darwin ; then
126 | javaHome="`dirname \"$javaExecutable\"`"
127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
128 | else
129 | javaExecutable="`readlink -f \"$javaExecutable\"`"
130 | fi
131 | javaHome="`dirname \"$javaExecutable\"`"
132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
133 | JAVA_HOME="$javaHome"
134 | export JAVA_HOME
135 | fi
136 | fi
137 | fi
138 |
139 | if [ -z "$JAVACMD" ] ; then
140 | if [ -n "$JAVA_HOME" ] ; then
141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
142 | # IBM's JDK on AIX uses strange locations for the executables
143 | JAVACMD="$JAVA_HOME/jre/sh/java"
144 | else
145 | JAVACMD="$JAVA_HOME/bin/java"
146 | fi
147 | else
148 | JAVACMD="`which java`"
149 | fi
150 | fi
151 |
152 | if [ ! -x "$JAVACMD" ] ; then
153 | echo "Error: JAVA_HOME is not defined correctly." >&2
154 | echo " We cannot execute $JAVACMD" >&2
155 | exit 1
156 | fi
157 |
158 | if [ -z "$JAVA_HOME" ] ; then
159 | echo "Warning: JAVA_HOME environment variable is not set."
160 | fi
161 |
162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
163 |
164 | # traverses directory structure from process work directory to filesystem root
165 | # first directory with .mvn subdirectory is considered project base directory
166 | find_maven_basedir() {
167 |
168 | if [ -z "$1" ]
169 | then
170 | echo "Path not specified to find_maven_basedir"
171 | return 1
172 | fi
173 |
174 | basedir="$1"
175 | wdir="$1"
176 | while [ "$wdir" != '/' ] ; do
177 | if [ -d "$wdir"/.mvn ] ; then
178 | basedir=$wdir
179 | break
180 | fi
181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
182 | if [ -d "${wdir}" ]; then
183 | wdir=`cd "$wdir/.."; pwd`
184 | fi
185 | # end of workaround
186 | done
187 | echo "${basedir}"
188 | }
189 |
190 | # concatenates all lines of a file
191 | concat_lines() {
192 | if [ -f "$1" ]; then
193 | echo "$(tr -s '\n' ' ' < "$1")"
194 | fi
195 | }
196 |
197 | BASE_DIR=`find_maven_basedir "$(pwd)"`
198 | if [ -z "$BASE_DIR" ]; then
199 | exit 1;
200 | fi
201 |
202 | ##########################################################################################
203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
204 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
205 | ##########################################################################################
206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
207 | if [ "$MVNW_VERBOSE" = true ]; then
208 | echo "Found .mvn/wrapper/maven-wrapper.jar"
209 | fi
210 | else
211 | if [ "$MVNW_VERBOSE" = true ]; then
212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
213 | fi
214 | if [ -n "$MVNW_REPOURL" ]; then
215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar"
216 | else
217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.3/maven-wrapper-0.5.3.jar"
218 | fi
219 | while IFS="=" read key value; do
220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
221 | esac
222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
223 | if [ "$MVNW_VERBOSE" = true ]; then
224 | echo "Downloading from: $jarUrl"
225 | fi
226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
227 | if $cygwin; then
228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
229 | fi
230 |
231 | if command -v wget > /dev/null; then
232 | if [ "$MVNW_VERBOSE" = true ]; then
233 | echo "Found wget ... using wget"
234 | fi
235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
236 | wget "$jarUrl" -O "$wrapperJarPath"
237 | else
238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
239 | fi
240 | elif command -v curl > /dev/null; then
241 | if [ "$MVNW_VERBOSE" = true ]; then
242 | echo "Found curl ... using curl"
243 | fi
244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
245 | curl -o "$wrapperJarPath" "$jarUrl" -f
246 | else
247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
248 | fi
249 |
250 | else
251 | if [ "$MVNW_VERBOSE" = true ]; then
252 | echo "Falling back to using Java to download"
253 | fi
254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
255 | # For Cygwin, switch paths to Windows format before running javac
256 | if $cygwin; then
257 | javaClass=`cygpath --path --windows "$javaClass"`
258 | fi
259 | if [ -e "$javaClass" ]; then
260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
261 | if [ "$MVNW_VERBOSE" = true ]; then
262 | echo " - Compiling MavenWrapperDownloader.java ..."
263 | fi
264 | # Compiling the Java class
265 | ("$JAVA_HOME/bin/javac" "$javaClass")
266 | fi
267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
268 | # Running the downloader
269 | if [ "$MVNW_VERBOSE" = true ]; then
270 | echo " - Running MavenWrapperDownloader.java ..."
271 | fi
272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
273 | fi
274 | fi
275 | fi
276 | fi
277 | ##########################################################################################
278 | # End of extension
279 | ##########################################################################################
280 |
281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
282 | if [ "$MVNW_VERBOSE" = true ]; then
283 | echo $MAVEN_PROJECTBASEDIR
284 | fi
285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
286 |
287 | # For Cygwin, switch paths to Windows format before running java
288 | if $cygwin; then
289 | [ -n "$M2_HOME" ] &&
290 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
291 | [ -n "$JAVA_HOME" ] &&
292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
293 | [ -n "$CLASSPATH" ] &&
294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
295 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
297 | fi
298 |
299 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
300 |
301 | exec "$JAVACMD" \
302 | $MAVEN_OPTS \
303 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
304 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
305 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
306 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------