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.Item;
|
||||
import com.rviewer.skeletons.domain.service.CartService;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.request.CreateCartReq;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.request.UpdateCartReq;
|
||||
import com.rviewer.skeletons.application.request.CreateCartReq;
|
||||
import com.rviewer.skeletons.application.request.UpdateCartReq;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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.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.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.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 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 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;
|
||||
|
||||
public interface DatabasePort {
|
||||
public interface CartDatabasePort {
|
||||
|
||||
Optional<Cart> get(String id);
|
||||
void save(Cart cart);
|
||||
@@ -1,8 +1,10 @@
|
||||
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.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 javax.transaction.Transactional;
|
||||
@@ -11,24 +13,34 @@ import java.util.Optional;
|
||||
@Component
|
||||
@Transactional
|
||||
public class CartService implements CartServicePort {
|
||||
private final DatabasePort databasePort;
|
||||
private final CartDatabasePort cartDatabasePort;
|
||||
private final Blockchain blockchain;
|
||||
|
||||
public CartService(DatabasePort databasePort) {
|
||||
this.databasePort = databasePort;
|
||||
public CartService(CartDatabasePort cartDatabasePort, Blockchain blockchain) {
|
||||
this.cartDatabasePort = cartDatabasePort;
|
||||
this.blockchain = blockchain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Cart> get(String id) {
|
||||
return databasePort.get(id);
|
||||
return cartDatabasePort.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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.ports.secondary.DatabasePort;
|
||||
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
|
||||
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
|
||||
import com.rviewer.skeletons.domain.ports.secondary.CartDatabasePort;
|
||||
import com.rviewer.skeletons.infrastructure.database.dto.PostgresCart;
|
||||
import com.rviewer.skeletons.infrastructure.database.repository.PostgresCartRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class PostgresAdapter implements DatabasePort {
|
||||
public class CartDatabaseAdapter implements CartDatabasePort {
|
||||
|
||||
private final PostgresCartRepository postgresCartRepository;
|
||||
|
||||
public PostgresAdapter(PostgresCartRepository postgresCartRepository) {
|
||||
public CartDatabaseAdapter(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 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 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.stereotype.Repository;
|
||||
|
||||
@@ -2,7 +2,7 @@ server.port=8080
|
||||
application.title=Cartfidential
|
||||
application.version=1.0.0
|
||||
|
||||
spring.datasource.initialization-mode=always
|
||||
spring.sql.init.mode=always
|
||||
spring.datasource.username = rv_user
|
||||
spring.datasource.password = rv_password
|
||||
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.Item;
|
||||
import com.rviewer.skeletons.domain.service.CartService;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartAlreadyExistsException;
|
||||
import com.rviewer.skeletons.infrastructure.inbound.api.exception.CartNotFoundException;
|
||||
import com.rviewer.skeletons.application.exception.CartAlreadyExistsException;
|
||||
import com.rviewer.skeletons.application.exception.CartNotFoundException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
@@ -21,7 +21,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.assertTrue;
|
||||
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.Item;
|
||||
import com.rviewer.skeletons.infrastructure.outbound.database.adapter.PostgresAdapter;
|
||||
import com.rviewer.skeletons.infrastructure.outbound.database.dto.PostgresCart;
|
||||
import com.rviewer.skeletons.infrastructure.outbound.database.repository.PostgresCartRepository;
|
||||
import com.rviewer.skeletons.infrastructure.database.adapter.CartDatabaseAdapter;
|
||||
import com.rviewer.skeletons.infrastructure.database.dto.PostgresCart;
|
||||
import com.rviewer.skeletons.infrastructure.database.repository.PostgresCartRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -23,16 +23,16 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringBootTest
|
||||
class PostgresAdapterTest {
|
||||
class CartDatabaseAdapterTest {
|
||||
|
||||
@Autowired
|
||||
private PostgresAdapter postgresAdapter;
|
||||
private CartDatabaseAdapter cartDatabaseAdapter;
|
||||
@Mock
|
||||
private PostgresCartRepository postgresCartRepository;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
postgresAdapter = new PostgresAdapter(postgresCartRepository);
|
||||
cartDatabaseAdapter = new CartDatabaseAdapter(postgresCartRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -43,7 +43,7 @@ class PostgresAdapterTest {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class PostgresAdapterTest {
|
||||
Item item = new Item(UUID.randomUUID(), "item", 1, 100F);
|
||||
Cart newCart = new Cart(id, List.of(item));
|
||||
|
||||
postgresAdapter.save(newCart);
|
||||
cartDatabaseAdapter.save(newCart);
|
||||
|
||||
ArgumentCaptor<PostgresCart> captor = ArgumentCaptor.forClass(PostgresCart.class);
|
||||
verify(postgresCartRepository).save(captor.capture());
|
||||
@@ -68,7 +68,7 @@ class PostgresAdapterTest {
|
||||
|
||||
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());
|
||||
}
|
||||
Reference in New Issue
Block a user