Added application layer and blockchain implementation

This commit is contained in:
droideparanoico
2022-10-08 12:27:21 +02:00
parent 63fb2fd1c6
commit 1a030c75b1
18 changed files with 140 additions and 41 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,7 @@
package com.rviewer.skeletons.domain.exception;
public class HashGenerationException extends RuntimeException {
public HashGenerationException(Throwable cause) {
super("Unable generate hash: " + cause);
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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()));
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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());
}