Added application layer and blockchain implementation
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.adapter;
|
package com.rviewer.skeletons.application.adapter;
|
||||||
|
|
||||||
|
import com.rviewer.skeletons.application.exception.CartAlreadyExistsException;
|
||||||
|
import com.rviewer.skeletons.application.exception.CartNotFoundException;
|
||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import com.rviewer.skeletons.domain.service.CartService;
|
import com.rviewer.skeletons.domain.service.CartService;
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
|
import com.rviewer.skeletons.application.request.CreateCartReq;
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
|
import com.rviewer.skeletons.application.request.UpdateCartReq;
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.request.CreateCartReq;
|
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.request.UpdateCartReq;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
|
package com.rviewer.skeletons.application.exception;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
|
package com.rviewer.skeletons.application.exception;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.exception;
|
package com.rviewer.skeletons.application.exception;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.request;
|
package com.rviewer.skeletons.application.request;
|
||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.inbound.api.request;
|
package com.rviewer.skeletons.application.request;
|
||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.rviewer.skeletons.domain.exception;
|
||||||
|
|
||||||
|
public class HashGenerationException extends RuntimeException {
|
||||||
|
public HashGenerationException(Throwable cause) {
|
||||||
|
super("Unable generate hash: " + cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/com/rviewer/skeletons/domain/model/Block.java
Normal file
52
src/main/java/com/rviewer/skeletons/domain/model/Block.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package com.rviewer.skeletons.domain.model;
|
||||||
|
|
||||||
|
import com.rviewer.skeletons.domain.exception.HashGenerationException;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.With;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@With
|
||||||
|
public class Block {
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
private String lasthash;
|
||||||
|
private String data;
|
||||||
|
private Integer nonce;
|
||||||
|
private String hash;
|
||||||
|
|
||||||
|
public Block() {
|
||||||
|
this.timestamp = LocalDateTime.now();
|
||||||
|
this.nonce = new Random().nextInt();
|
||||||
|
this.hash = generateHashFromBlock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Block getGenesisBlock() {
|
||||||
|
return new Block().withLasthash("");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String generateHashFromBlock(final Block block) throws HashGenerationException {
|
||||||
|
try {
|
||||||
|
final var digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
final byte[] hash = digest.digest(block.getHashData().getBytes(StandardCharsets.UTF_8));
|
||||||
|
final var hexString = new StringBuilder();
|
||||||
|
for (final byte elem : hash) {
|
||||||
|
final var hex = Integer.toHexString(0xff & elem);
|
||||||
|
if (hex.length() == 1) hexString.append('0');
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new HashGenerationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHashData() {
|
||||||
|
return timestamp + lasthash + data + nonce;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.rviewer.skeletons.domain.model;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Blockchain {
|
||||||
|
private final LinkedList<Block> blockchainList = new LinkedList<>();
|
||||||
|
|
||||||
|
public LinkedList<Block> getBlockchainList() {
|
||||||
|
return blockchainList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Block addBlock(Block block) {
|
||||||
|
blockchainList.add(block);
|
||||||
|
return blockchainList.getLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean replace(List<Block> blockchainList) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isValid(List<Block> blockchainList) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import com.rviewer.skeletons.domain.model.Cart;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface DatabasePort {
|
public interface CartDatabasePort {
|
||||||
|
|
||||||
Optional<Cart> get(String id);
|
Optional<Cart> get(String id);
|
||||||
void save(Cart cart);
|
void save(Cart cart);
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.rviewer.skeletons.domain.service;
|
package com.rviewer.skeletons.domain.service;
|
||||||
|
|
||||||
|
import com.rviewer.skeletons.domain.model.Block;
|
||||||
|
import com.rviewer.skeletons.domain.model.Blockchain;
|
||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import com.rviewer.skeletons.domain.ports.primary.CartServicePort;
|
import com.rviewer.skeletons.domain.ports.primary.CartServicePort;
|
||||||
import com.rviewer.skeletons.domain.ports.secondary.DatabasePort;
|
import com.rviewer.skeletons.domain.ports.secondary.CartDatabasePort;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
@@ -11,24 +13,34 @@ import java.util.Optional;
|
|||||||
@Component
|
@Component
|
||||||
@Transactional
|
@Transactional
|
||||||
public class CartService implements CartServicePort {
|
public class CartService implements CartServicePort {
|
||||||
private final DatabasePort databasePort;
|
private final CartDatabasePort cartDatabasePort;
|
||||||
|
private final Blockchain blockchain;
|
||||||
|
|
||||||
public CartService(DatabasePort databasePort) {
|
public CartService(CartDatabasePort cartDatabasePort, Blockchain blockchain) {
|
||||||
this.databasePort = databasePort;
|
this.cartDatabasePort = cartDatabasePort;
|
||||||
|
this.blockchain = blockchain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Cart> get(String id) {
|
public Optional<Cart> get(String id) {
|
||||||
return databasePort.get(id);
|
return cartDatabasePort.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(Cart cart) {
|
public void save(Cart cart) {
|
||||||
databasePort.save(cart);
|
cartDatabasePort.save(cart);
|
||||||
|
if (blockchain.getBlockchainList().isEmpty()) {
|
||||||
|
blockchain.addBlock(Block.getGenesisBlock().withData(cart.toString()));
|
||||||
|
} else {
|
||||||
|
blockchain.addBlock(new Block().withData(cart.toString())
|
||||||
|
.withLasthash(blockchain.getBlockchainList().getLast().getHash()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
databasePort.delete(id);
|
cartDatabasePort.delete(id);
|
||||||
|
blockchain.addBlock(new Block().withData("deleted cart with id:" + id)
|
||||||
|
.withLasthash(blockchain.getBlockchainList().getLast().getHash()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.outbound.database.adapter;
|
package com.rviewer.skeletons.infrastructure.database.adapter;
|
||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import com.rviewer.skeletons.domain.ports.secondary.DatabasePort;
|
import com.rviewer.skeletons.domain.ports.secondary.CartDatabasePort;
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
|
import com.rviewer.skeletons.infrastructure.database.dto.PostgresCart;
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
|
import com.rviewer.skeletons.infrastructure.database.repository.PostgresCartRepository;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class PostgresAdapter implements DatabasePort {
|
public class CartDatabaseAdapter implements CartDatabasePort {
|
||||||
|
|
||||||
private final PostgresCartRepository postgresCartRepository;
|
private final PostgresCartRepository postgresCartRepository;
|
||||||
|
|
||||||
public PostgresAdapter(PostgresCartRepository postgresCartRepository) {
|
public CartDatabaseAdapter(PostgresCartRepository postgresCartRepository) {
|
||||||
this.postgresCartRepository = postgresCartRepository;
|
this.postgresCartRepository = postgresCartRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.outbound.database.dto;
|
package com.rviewer.skeletons.infrastructure.database.dto;
|
||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.outbound.database.dto;
|
package com.rviewer.skeletons.infrastructure.database.dto;
|
||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.rviewer.skeletons.infrastructure.outbound.database.repository;
|
package com.rviewer.skeletons.infrastructure.database.repository;
|
||||||
|
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
|
import com.rviewer.skeletons.infrastructure.database.dto.PostgresCart;
|
||||||
import org.springframework.data.repository.CrudRepository;
|
import org.springframework.data.repository.CrudRepository;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ server.port=8080
|
|||||||
application.title=Cartfidential
|
application.title=Cartfidential
|
||||||
application.version=1.0.0
|
application.version=1.0.0
|
||||||
|
|
||||||
spring.datasource.initialization-mode=always
|
spring.sql.init.mode=always
|
||||||
spring.datasource.username = rv_user
|
spring.datasource.username = rv_user
|
||||||
spring.datasource.password = rv_password
|
spring.datasource.password = rv_password
|
||||||
spring.datasource.driverClassName = org.postgresql.Driver
|
spring.datasource.driverClassName = org.postgresql.Driver
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import com.rviewer.skeletons.domain.service.CartService;
|
import com.rviewer.skeletons.domain.service.CartService;
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
|
import com.rviewer.skeletons.application.exception.CartAlreadyExistsException;
|
||||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
|
import com.rviewer.skeletons.application.exception.CartNotFoundException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
@@ -21,7 +21,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static com.rviewer.skeletons.infrastructure.inbound.api.exception.ControllerExceptionHandler.INVALID_BODY;
|
import static com.rviewer.skeletons.application.exception.ControllerExceptionHandler.INVALID_BODY;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.rviewer.skeletons.infrastructure.outbound.adapter;
|
|||||||
|
|
||||||
import com.rviewer.skeletons.domain.model.Cart;
|
import com.rviewer.skeletons.domain.model.Cart;
|
||||||
import com.rviewer.skeletons.domain.model.Item;
|
import com.rviewer.skeletons.domain.model.Item;
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.adapter.PostgresAdapter;
|
import com.rviewer.skeletons.infrastructure.database.adapter.CartDatabaseAdapter;
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
|
import com.rviewer.skeletons.infrastructure.database.dto.PostgresCart;
|
||||||
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
|
import com.rviewer.skeletons.infrastructure.database.repository.PostgresCartRepository;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
@@ -23,16 +23,16 @@ import static org.mockito.Mockito.verify;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class PostgresAdapterTest {
|
class CartDatabaseAdapterTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PostgresAdapter postgresAdapter;
|
private CartDatabaseAdapter cartDatabaseAdapter;
|
||||||
@Mock
|
@Mock
|
||||||
private PostgresCartRepository postgresCartRepository;
|
private PostgresCartRepository postgresCartRepository;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init() {
|
void init() {
|
||||||
postgresAdapter = new PostgresAdapter(postgresCartRepository);
|
cartDatabaseAdapter = new CartDatabaseAdapter(postgresCartRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -43,7 +43,7 @@ class PostgresAdapterTest {
|
|||||||
|
|
||||||
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(expectedCart)));
|
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(expectedCart)));
|
||||||
|
|
||||||
Optional<Cart> result = postgresAdapter.get(id.toString());
|
Optional<Cart> result = cartDatabaseAdapter.get(id.toString());
|
||||||
assertThat(Optional.of(result)).hasValue(Optional.of(expectedCart));
|
assertThat(Optional.of(result)).hasValue(Optional.of(expectedCart));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class PostgresAdapterTest {
|
|||||||
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
|
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
|
||||||
Cart newCart = new Cart(id, List.of(item));
|
Cart newCart = new Cart(id, List.of(item));
|
||||||
|
|
||||||
postgresAdapter.save(newCart);
|
cartDatabaseAdapter.save(newCart);
|
||||||
|
|
||||||
ArgumentCaptor<PostgresCart> captor = ArgumentCaptor.forClass(PostgresCart.class);
|
ArgumentCaptor<PostgresCart> captor = ArgumentCaptor.forClass(PostgresCart.class);
|
||||||
verify(postgresCartRepository).save(captor.capture());
|
verify(postgresCartRepository).save(captor.capture());
|
||||||
@@ -68,7 +68,7 @@ class PostgresAdapterTest {
|
|||||||
|
|
||||||
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(cartToDelete)));
|
when(postgresCartRepository.findById(id)).thenReturn(Optional.ofNullable(PostgresCart.fromDomain(cartToDelete)));
|
||||||
|
|
||||||
postgresAdapter.delete(cartToDelete.getId().toString());
|
cartDatabaseAdapter.delete(cartToDelete.getId().toString());
|
||||||
|
|
||||||
verify(postgresCartRepository, times(1)).deleteById(cartToDelete.getId());
|
verify(postgresCartRepository, times(1)).deleteById(cartToDelete.getId());
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user