-
-
Save ZapDos7/7ace311e2615289b31e33bb7c6ffe6b7 to your computer and use it in GitHub Desktop.
- We include a
pom.xml
(POM = Project Object Model) file which contains data about files & dependencies so it auto handles them when needed)
It is something produced or used by the project e.g. JARs, source/binary distr etc)
mvn archetype:generate
--DgroupId=X
// universally unique identified for project e.g. company name --DartifactId=Y
// directory/name of app --DarchetypeVersion=W
-extension
-> type -classifier
-> type - The ones above are the artifact ID. Also we can add: --DinteractiveMode=false
Y
└ pom.xml <- helps parser, it's based on schema
└ src/
└ main
└ java
└ com
└ X (my company)
└ app
└ Application.java
└ test
└ java
└ com
└ X (my company)
└ app
└ TestApplication.java
Step in build lifecycle = phase (not goal)
Each level does all the previous ones:
mvn compile
:
- validate
- generate sources
- process sources
- generate resources
- process resources
- compile
java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
validate
(is project correct? is all necessary info available?)compile
test
(using suitable unit testing framework - should not require the code to be packaged/deployed)package
(compiled code: JAR or other distributed format)integration-test
(process & deploy the package if needed in environment: integration tests)verify
(if package is valid and meets quality data)install
(install to local repo: use as dependency in other projects locally)deploy
(done integration/release env copies final package to remote repo)
clean
(cleans up artifacts created by prior builds)site
(gemerates site documentation for this project in javadoc format)
- MOJO (Σ(MOJO) = plugin) is a Java class that implements the org.apache maven plugin Mojo Interface (it's the implementation of a goal)
- Snapshot: indicates WIP
- APT: format of documentation
- XDoc: format of documentation
Need: group ID, artifact ID, version. Optionally: scope, type, optional (they have default values)
compile
: needed to workprovided
: needed but if already exists in package, not (e.g. Tomcat)test
: only for test libs, not for final deployment (e.g. JUnit)runtime
: compiler may not need it but it's needed on runtimesystem
import
- soft:
1.0
(earlier works too) - hard:
[1.0]
(the only one needed)
e.g. [1.0]
exclude this because 1.7
is better:
home/.m2/
where our JARs reside/target/folder/
where compiled works reside
They hold the build artifacts & dependencies)
- local (computer directory where maven runs - all you should do is delete)
- remote (fully remote or just shared on server in company etc - upload/download)
- downloaded when missing depedency (
pom.xml
specify mirrors if needed)
- downloaded when missing depedency (
mvn -o package
- Spring simplifies Java development
- 4 strategies:
- lightweight & minimally invasive development with POJOs (plain old Java object)
- loose coupling thgouth DI (dependency injection) and interface orientation
- declarative programming through aspects and common conventions
- eliminating boilerplate code (repetitive) with aspects templates
Traditionally, each object is responsible for obtaining its own references to the objects it collaborates with (= its dependencies), which leads to highly coulped and hard to test code. With DI, objects are given their dependencies at creation time by some third party that coordinates each object in the system.
+---+ ┌--injected into-- A
| C | <┤
+---+ └--injected into-- B
e.g. construction injection (select interface at object constructor => loose coupling (only know interface, not implementation)
- Mock object framework: Mockito to mock implementation of interfaces for easier testing
- Wiring: create associations between app components (commonly via xml or Java) => we can change the dependencies without changing the depending classes
- Application context: loads bean definitions and wires them together and it is fully responsible
Enables us to capture the functionality used throughout the application in reusable components
- Cross cutting concerns: component with system services needed but aren't their cofre responsibility (these services span some components)
- This leads to higher complexity, duplicate code between components even if abstracted module, so single method call, that call is duplicated in multiple places)
- Components littered with code outside their functionality
- So AOP (POJOs remain plain, components are cohesive and focused on their job and ignorant of any involved system calls)
- Aspects declared by spring context only
- creates the objects, wires them, configures and manages their lifecycle
- uses DI to manage the components that make up an applications (including creating associations between collaborating components)
- leads to cleaner code
- easier to understand
- easier to unit test
- easier to reuse
- 2 types of container implementations:
- bean factory (simplest containers, provide support for DI, often too low level)
- application contexts (build in the notion of a bean factory by providing application-framework services - resolve textual messages etc)
- Instantiate
- inject values
- Populate properties
- if implements beanId:
setBeanName()
- if implements beanId:
- (Aware)
setBeanName()
- (Aware)
setBeanFactory()
- passes reference to the enclosing app context
- (Aware)
setApplicationContext()
- Pre init bean post procs
AfterPropertiesSet()
- Custom
init
- Post init bean post procs
- Bean is ready to use!
- ..
destroy()
disposable bean- Call custom
destroy()
- END
3 ways (we can also use more than one):
- explicit configuration in XML (only for convenient XML namespace without equivalent)
- explicit configuration in Java (better than XML)
- implicit bean discovery & automatic wiring (recommended)
These ways can also work together
- component scanning: automatically discovers beans to be created in the application context (
@ComponentScan
,@Component
) - autowiring: automatically satisfies bean dependencies (
@Autowired
works on constructors & setters and any method in class, works like@Injected
: instantiate bean & pass in a bean assignable to corresponding class/interface: Spring tries to satisfy the dependency expressed in methods parameters (1 bean matches so 1 bean will be wired on), if many beans match, ambiguity exception, if no matching beans, throws exception unlessrequired=false
(check for NPE in code!))
For Testing, we use SpringJUnit4ClassRunner
: automatically a Spring application context is created when the test starts
- Core Container - core (fundamental parts like IoC & DI) - bean (BeanFactory - factory pattern) - Context (solid base - medium to access any objects defined and configured - application context) - SpEL (expression language for querying & manipulating object graph on runtime)
- Data Access/Integration - JDBC (adds abbstraction/layer) - ORM (adds integration layers for object relational mapping APIs like JPA, JDO, Hibernate...)) - OXM (adds abstraction layer for object/XML implementation JAXB, Castor...) - JMS (Java Messaging Service: producing and consuming messages) - Transaction (supports programmatic declarative transaction managment for classes that implement special interfaces & all PJOs)
- Web - web (basic web oriented integration features) - web-MVC - web-socket - wev-portlet
- Misc - AOP - Aspects (integration with AspectJS-AOP tool) - Instrumentation - Messaging (support for STOMP as the web socket sub protocol) - Testing
- Redis: saves values as key0value set
- Spring: container of singleton beans (only 1 copy is needed) as desired by creator - IoC
application.yml
: read by app once it is booted (last first)bootstrap.yml
: read by app once it is booted (first first)- Jetty = lightweight Tomcat
- mongeez for mongo db management
- Spring in Action
= The act of balancing the "load" or traffic to an application across multiple instances or servers
-
With GCP, load balancing is a software cloud-based offering to automatically facilitate shift or traffic from one instance to another when traffic or load is increased.
-
Has 8 load balancer offerings:
A. Global
- global external HTTP(S) load balancing (supports applications deployed in multiple regions (even if in the same country) that receives HTTP(s) traffic from the internet.)
- external HTTP(S) load balancing (classic)
- SSL proxy load balancing (also external)
- TCP proxy load balancing (also external - it "proxies" the traffic meaning it does not directly provide source/destination details to the instance, returns the response from the instance to the client, and requires 2 connections: one from the client to the load balancer and another from the load balancer to the instance.)
B. Regional
- regional external HTTP(S) load balancing (external - instances deployed in one region only, receives HTTP traffic from the public internet)
- internal HTTP(S) load balancing (HTTP traffic e.g. back end APIs)
- internal TCP/UDP load balancing (TCP traffic - no data loss & UDP traffic - time is of the essence)
- external TCP/UDP network load balancing (supports TCP, UDP, ESP & ICMP traffic)
-
Categories
- Flow of traffic (external traffic from internet to application (=> external facing IP open for anyone to send traffic to) or internal traffic between internal instances on same network (intenral-facing IP address))
- Traffic type (type of protocol used e.g. HTTP, TCP, UDP)
- Global or regional
-
Receives HTTP traffic from the public internet
-
Has instances deployed accross multiple regions
-
GCP Offerings:
- Classic/Legacy: stable release of global load balancer
- New & Improved: preview stage load balancer (only for test environments)
-
The new version provides three advances traffic management features:
- Traffic Policies (configure behavior & algorithms)
- Traffic Actions (perform actions based on request & response)
- Traffic Steering (route traffic based on request params)
-
How to setup:
- create an instance template
- it ensures the same configuration files are used each time
- it is required to set up a managed instance group and a managed instance group is required before we can set up a load balancer (having our instances be managed through a group will allow us to configure our load balancer to shift traffic between instances when needed)
- GCP console > Computer Engine > Instance Templates > Create instance template (fill in info - you should add a network tag for health checks)
Note: Under
Management
we can define the startup script.
- deploy a managed instance group (optionally configured with a health check)
- GCP console > Computer Engine > Health Checks > Create Health Check
- Here we define scope (global/regional), protocol (HTTP, TCP etc), we could also define a request path used in our app specifically for smoke tests
- Health criteria (check interval, timeout, etc)
- configure firewall rules to accept health check probes (if needed)
- VCP Network > Firewall > Create firewall rule.
- Here we also define the specifics of the firewall; for this case, we need
ingress
direction of traffic (internal requests). We also need to define the IP addresses google uses, thus allowing traffic only from those systems - Health Check Documentation
- Managed Instance Groups
- GCP Console > Computer Engine > Instance Group > Create new Instance group
- Select between stateful/stateless, define location, autohealing (select one of defined health check)
- create a load balancer & configure it to receive external traffic
- GCP Console > VCP Network > IP Addresses
- Here we can reserve an external static address (we can select type global/regional).
- Now, the address is reserved and can be used elsewhere.
- GCP Console > Network Services > Load Balancer > Create load balancer > select type > Start configuration (internet facing on internal only & global (static or new) or regional).
- We give a name & define back end service(s) (using the predefined instance groups & health checks)
- Then we define host & path rules
- FE configuration
- Eventually we review the set up & finalize the configuration, which creates the load balancer. Now ths FE's IP address is what the services will reach out to.
- test load balancer
- Using the aforementioned IP we can
POST http://ip.address:port/path
in order to verify the load balancer is working. - In order to verify propetly we need to "kill" some of the instances, thus mimicking loaded conditions for our load balancer.
- GCP Console > Compute Engines > VM Instances > check IPs, ssh there, kill the proper process & try anew.
- Using the aforementioned IP we can
- create an instance template
- Hardware & Media
- Network
- OS
- Applications *
- process of verifying principal (can be a human or a system)
- support by spring:
- determines what the principal can or cannot do
- it is based on authentication
- it is also called access control
- support:
- web request
- method invocation
- domain object access control
<!-- Main -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
Java Config:
package com.frankmoley.lil.adminweb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter /* !! */ {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user); // only for initial testing purposes - not scalable, NEVER in prod
}
}
. Add to schema/db a table
users`
CREATE TABLE users
(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(500) not null,
enabled boolean not null
);
CREATE TABLE authorities
(
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key (username) references users (username)
);
CREATE UNIQUE INDEX ix_auth_username on authorities (username, authority);
- Authorization server: supreme architectural component for web API security. Acts as centralization authorization points that allows apps & HTTP endpoints to identify the features of our app
- Resource server: application that provides the access token to the clients to access the Resource server HTTP endpoints. It's a collection of libraries {HTTP endpoints, static resources, dynamic webpages}
- OAuth2: authorization framework thath enables the application web security to access the resources from the client
Grant Type (auth code), client ID, client secret
- JWT: JSON Web Token, represents the claims secured between 2 parties
Provider:
- responsible for exposing OAuth 2.0 protected resources
- configuration
- create OAuth 2.0 clients who can access resources (independently or on behalf of user)
- manage and evaluate tokens used for access (& where applicable, provider must supply interface for user to confirm granting access - confirmation page)
IoC -> DI for specific lightweight containers with IoC of a UI
ClassName -----> Interface
| Λ
| |
└-- creates-> Implementator
ClassName -----> Interface <--------------------┐
Λ Λ |
| | |
| Implementator <--- creates --- Assembler
└-----------------------------------------------┘
- IoC to interface injection
- IoC to setter injection
- IoC to constructor injection
Service locator: an object or method that knows how to get hold of all the services that an app might need
┌----------> Service Locator <------------------┐
| |
| |
ClassName -----> Interface <--------------------┤
Λ |
| |
Implementator <--- creates --- Assembler
You don't need the entire service locator: reduce by using a role interface, declare just the bit of interface needed
- both provide decoupling but their difference: how the implementation is provided to the app class
- service locator: asks explicitly by a message
- DI: no explicit request (service appears to app class)
Spring Events course by Terezija Semenski
Observer pattern implemented in Spring basically.
- introduce loose coupling => flexibility
- simpler testing process
- changes in one component do not affect the others
- provides publush/subscribe capabilities
- publishers & subscribers are not tied up
It also follows open-closed principle (open for extension, closed for modification)
Is a simple pojo
class CustomEvent {
String name;
...
}
Constructs & publishes event objects
@Autowired ApplicationEventPublisher publisher;
void method() {
publisher.publishEvent(new CustomEvent());
}
Can be implemented with an annotation:
class MyListener {
@EventListener public void onEvent(Object o) {}
}
- Each method will be automatically registered as a new application listener to listen for one or many events, depending on the signature of the method.
- have non void return type (result is sent as a new event - if returns collection, each element is sent as a new individual event)
- can define order to be invoked using
Order(X)
(X = int, order number)
or by implementing ApplicationListener
interface:
class MyListener implements ApplicationListener<ApplicationEvent> {
@Override public void onApplicationEvent(ApplicationEvent e) {}
}
- With the latter form as long as it is registered as a Spring bean, it will receive events.
- Spring uses the signature of the listener to determine if it matches that event or not.
- It can only be used for objects that extend
ApplicationEvent
class. - Can only process one event type.
- can define order to be invoked using
Order(X)
(X = int, order number)
All Listeners are automatically registered
Synchronous events (default): the publisher thread blocks until all listeners have finished listening the event. Asynchronous events: the event is published in a new thread & the execution is independent from the listener.
Spring allows to bind an event to a phase of transaction
+================+
| @Transactional |
+================+ --------> BEFORE_COMMIT
| commit |
+----------------+ --------> AFTER_COMMIT
| rollback |
+----------------+ --------> AFTER_ROLLBACK
| completion |
+================+ --------> AFTER_COMPLETION
Spring allows listeners to listen under conditions
Application - tied to application states
- ApplicationStartingEvent
- ApplicationEnvironmentPreparedEvent
- ApplicationContextInitializedEvent
- ApplicationPreparedEvent
- ApplicationStartedEvent
Context - tied to application context
- ContextRefreshedEvent
- ContextStartededEvent
- ContextStoppedEvent
- ContextClosedEvent
If we need listeners before the context is created, we need to add them manually:
public static void main(String[] args) {
SpringApplication springApp = new SpringApplication(Application.class);
springApp.addListeners(new MyListener());
springApp.run(args);
}
When creating a publisher, we can simply
// event
@Data
public class CustomerRegisteredEvent
{
private final Customer customer;
}
// Service - publisher
@Component
@RequiredArgsConstructor
public class CustomerService
{
private final CustomerRepository customerRepository;
private final ApplicationEventPublisher publisher;
public void register(Customer customer)
{
customerRepository.save(customer);
publisher.publishEvent(new CustomerRegisteredEvent(customer));
}
public void remove(Customer customer)
{
customerRepository.delete(customer);
}
}
// Service - listener
@Component
@RequiredArgsConstructor
public class EmailListeners
{
private final EmailService emailService;
@EventListener
public void onRegisterEvent(CustomerRegisteredEvent event)
{
emailService.sendRegisterEmail(event.getCustomer());
}
}
- Use
@Async
on listener needed to be executed asyncronously. - Add
@EnableAsync
on ourSpringBootApplication
- Cannot publish event by returning a value (if we need to, we can use
ApplicationEventPublisher.publish()
) - Exceptions are not propagated to the caller (but we can implement
AsyncUncaughtExceptionHandler
interface)
@Component
@RequiredArgsConstructor
public class AnalyticsCustomerRegisteredListener
{
private final AnalyticsService analyticsService;
@Async //!
@EventListener //!
public void onRegisterEvent(CustomerRegisteredEvent event)
{
analyticsService.registerNewCustomer(event.getCustomer());
}
}
@EventListener(condition="")
(condition in SPEL), evaluated if
- true
- "true", "on", "yes", "1"
- default = "" (always handled)
#event
to reference an event#event.customer.type eq 'b2c'
@myBean.test(#event)
// listener
@Component
@RequiredArgsConstructor
public class PromotionListeners
{
private final PromotionService promotionService;
@EventListener(condition = "#event.customer.newsletter")
public void onRegistrationEvent (CustomerRegisteredEvent event)
{
promotionService.applyPromotion(event.getCustomer());
}
}
- Spring allows us to bind an event listener to a phase of the current transaction.
- In case something goes wrong, rollback => consistent state
@Transactional
: combines more than one writes on a database as a single atomic operation.@TransactionalEventListener(phase=abc)
- event consumption is delayed until a certain transactional outcome (more fine grain control).abc
takes values:BEFORE_COMMIT
,AFTER_COMMIT
(default),AFTER_ROLLBACK
,AFTER_COMPLETION
.
-
We should opt to test native queries in repo level since they are the most prone to issues
-
We should select the components we wish to test so as to not load the entire application context needlessly & have more specific scope of testing.
-
Two types of testing:
- integration testing (when >1 teams) - top down
- else, bottom up
-
TDD - red green testing
-
To test only the service layer:
@SpringBootTest(WebEnvironment.NONE)
-
What to test in a controller?
- URL mapping
- Deserialize input
- Validate input
- Business logic (only this one is tested in Unit tests)
- Serialize output
- Translate exceptions
All 6 can be tested in integration tests
-
@WebMvcTest
orWebFlowTest
for controller testing, no need for HTTP resever,@MockBean
for services/repos -
@SpringBootTest
should not be overused (when the full application context isn't needed) -
To test HTTP interactions:
@MockMvc
-
To simulate slow conditions/chunked responses:
OkHttp MockWebServer
&WireMock
(mocks at HTTP level) -
If we mock all services:
- it's fast & easier BUT false assurance If we mock no service:
- accurate BUT slow and expensive
So we use
Spring Cloud Contract
, which uses stubs, defined contracts between services, gives fast feedback without a need for a full setup.
package com.frankmoley.lil.api.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.frankmoley.lil.api.data.entity.CustomerEntity;
import com.frankmoley.lil.api.data.repository.CustomerRepository;
import com.frankmoley.lil.api.web.model.Customer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ExtendWith(MockitoExtension.class)
public class CustomerServiceTest {
@InjectMocks // inject Mocked repo into Service
CustomerService customerService;
@Mock
CustomerRepository customerRepository;
@Test
void getAllCustomers(){
Mockito.doReturn(getMockCustomers(2)).when(customerRepository).findAll();
List<Customer> customers = this.customerService.getAllCustomers(); // implemented in service layer
assertEquals(2, customers.size()); // we added 2 customers in repository so the fetching method should return 2
}
private Iterable<CustomerEntity> getMockCustomers(int size){
List<CustomerEntity> customers = new ArrayList<>(size);
for(int i=0;i<size;i++){ // add dummy data
customers.add(new CustomerEntity(UUID.randomUUID(), "firstName" + i, "lastName" + i,
"email"+i+"@test.com", "555-515-1234", "1234 Main Street"));
}
return customers;
}
//
@Test
void getCustomer() {
CustomerEntity entity = getMockCustomerEntity();
Optional<CustomerEntity> optional = Optional.of(entity);
Mockito.doReturn(optional).when(customerRepository).findById(entity.getCustomerId());
Customer customer = service.getCustomer(entity.getCustomerId().toString());
assertNotNull(customer);
assertEquals("testFirst", customer.getFirstName());
}
@Test
void getCustomer_notExists() {
CustomerEntity entity = getMockCustomerEntity();
Optional<CustomerEntity> optional = Optional.empty();
Mockito.doReturn(optional).when(customerRepository).findById(entity.getCustomerId());
assertThrows(NotFoundException.class, ()->service.getCustomer(entity.getCustomerId().toString()), "exception not throw as expected");
// throw custom
}
@Test
void findByEmailAddress() {
CustomerEntity entity = getMockCustomerEntity();
Mockito.doReturn(entity).when(customerRepository).findByEmailAddress(entity.getEmailAddress());
Customer customer = service.findByEmailAddress(entity.getEmailAddress());
assertNotNull(customer);
assertEquals("testFirst", customer.getFirstName());
}
@Test
void addCustomer() {
CustomerEntity entity = getMockCustomerEntity();
when(customerRepository.findByEmailAddress(entity.getEmailAddress())).thenReturn(null);
when(customerRepository.save(any(CustomerEntity.class))).thenReturn(entity); // "any" -> fetch something, does not verify the customer is the one we needed because the ID is not set => we need more than unit tests!!!
Customer customer = new Customer(entity.getCustomerId().toString(), entity.getFirstName(), entity.getLastName(),
entity.getEmailAddress(), entity.getPhoneNumber(), entity.getAddress());
customer = service.addCustomer(customer);
assertNotNull(customer);
assertEquals("testLast", customer.getLastName());
}
@Test
void addCustomer_existing() {
CustomerEntity entity = getMockCustomerEntity();
when(customerRepository.findByEmailAddress(entity.getEmailAddress())).thenReturn(entity);
Customer customer = new Customer(entity.getCustomerId().toString(), entity.getFirstName(), entity.getLastName(),
entity.getEmailAddress(), entity.getPhoneNumber(), entity.getAddress());
assertThrows(ConflictException.class, () -> service.addCustomer(customer), "should have thrown conflict exception");
}
@Test
void updateCustomer() {
CustomerEntity entity = getMockCustomerEntity();
when(customerRepository.save(any(CustomerEntity.class))).thenReturn(entity); // any again
Customer customer = new Customer(entity.getCustomerId().toString(), entity.getFirstName(), entity.getLastName(),
entity.getEmailAddress(), entity.getPhoneNumber(), entity.getAddress());
customer = service.updateCustomer(customer);
assertNotNull(customer);
assertEquals("testLast", customer.getLastName());
}
@Test
void deleteCustomer() {
UUID id = UUID.randomUUID();
doNothing().when(customerRepository).deleteById(id); // doNothing() because void method
service.deleteCustomer(id.toString());
}
private CustomerEntity getMockCustomerEntity(){
return new CustomerEntity(UUID.randomUUID(), "testFirst", "testLast", "testemail@test.com", "555-515-1234", "1234 Test Street");
}
}
(For IntelliJ): Properly executed lines are indicated with green, whereas imporerly ones with red. This also works with Surefire or other plugins.
- Introduce
resources
folder where we introduce the test data (e.g. in sql files) - In
pom.xml
, add:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
- Code
-- schema
CREATE SCHEMA IF NOT EXISTS tdd;
CREATE TABLE IF NOT EXISTS tdd.CUSTOMERS (
CUSTOMER_ID UUID PRIMARY KEY,
FIRST_NAME VARCHAR,
LAST_NAME VARCHAR,
EMAIL VARCHAR UNIQUE,
PHONE VARCHAR,
ADDRESS VARCHAR
);
-- data
INSERT INTO tdd.CUSTOMERS (CUSTOMER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE, ADDRESS) VALUES ('054b145c-ddbc-4136-a2bd-7bf45ed1bef7','Cally','Reynolds','penatibus.et@lectusa.com','(901) 166-8355','556 Lakewood Park, Bismarck, ND 58505');
INSERT INTO tdd.CUSTOMERS (CUSTOMER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE, ADDRESS) VALUES ('9ac775c3-a1d3-4a0e-a2df-3e4ee8b3a49a','Sydney','Bartlett','nibh@ultricesposuere.edu','(982) 231-7357','4829 Badeau Parkway, Chattanooga, TN 37405');
INSERT INTO tdd.CUSTOMERS (CUSTOMER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE, ADDRESS) VALUES ('c04ca077-8c40-4437-b77a-41f510f3f185','Hunter','Newton','quam.quis.diam@facilisisfacilisis.org','(831) 996-1240','2 Rockefeller Avenue, Waco, TX 76796');
INSERT INTO tdd.CUSTOMERS (CUSTOMER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE, ADDRESS) VALUES ('3b6c3ecc-fad7-49db-a14a-f396ed866e50','Brooke','Perkins','sit@vitaealiquetnec.net','(340) 732-9367','87 Brentwood Park, Dallas, TX 75358');
INSERT INTO tdd.CUSTOMERS (CUSTOMER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE, ADDRESS) VALUES ('38124691-9643-4f10-90a0-d980bca0b27d','Nolan','Slater','sociis.natoque.penatibus@pedeCras.co.uk','(540) 487-5928','99 Sage Street, Reno, NV 89505');
and
package com.frankmoley.lil.api.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.frankmoley.lil.api.web.error.ConflictException;
import com.frankmoley.lil.api.web.error.NotFoundException;
import com.frankmoley.lil.api.web.model.Customer;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class CustomerServiceIntegrationTest {
@Autowired
private CustomerService service;
@Test
void getAllCustomers(){
List<Customer> customers = this.service.getAllCustomers();
assertEquals(5, customers.size()); // this passes if no changes in db OR if we cleanup after testing!!!
}
@Test
void getCustomer(){
Customer customer = this.service.getCustomer("054b145c-ddbc-4136-a2bd-7bf45ed1bef7");
assertNotNull(customer);
assertEquals("Cally", customer.getFirstName());
}
@Test
void getCustomer_NotFound(){
assertThrows(NotFoundException.class, () -> this.service.getCustomer("d972b30f-21cc-411f-b374-685ce23cd317"), "should have thrown an exception");
}
@Test
void addCustomer(){
Customer customer = new Customer("", "John", "Doe", "jdoe@test.com", "555-515-1234", "1234 Main Street; Anytown, KS 66110");
customer = this.service.addCustomer(customer);
assertTrue(StringUtils.isNotBlank(customer.getCustomerId()));
assertEquals("John", customer.getFirstName());
//cleaning up
this.service.deleteCustomer(customer.getCustomerId());
}
@Test
void addCustomer_alreadyExists(){
Customer customer = new Customer("", "John", "Doe", "penatibus.et@lectusa.com", "555-515-1234", "1234 Main Street; Anytown, KS 66110");
assertThrows(ConflictException.class, () -> this.service.addCustomer(customer));
}
@Test
void updateCustomer(){
Customer customer = new Customer("", "John", "Doe", "jdoe@test.com", "555-515-1234", "1234 Main Street; Anytown, KS 66110");
customer = this.service.addCustomer(customer);
customer.setFirstName("Jane");
customer = this.service.updateCustomer(customer);
assertEquals("Jane", customer.getFirstName());
//cleaning up
this.service.deleteCustomer(customer.getCustomerId());
}
}
Test the entire stack!
package com.frankmoley.lil.api.web.controller;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.frankmoley.lil.api.web.model.Customer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc // ---
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class CustomerControllerIntegrationTest {
@Autowired // ---
MockMvc mockMvc; //creates a pseudo dispatcher servlet which allows us to test the method
@Test
void getAllCustomers() throws Exception { // throwing exceptions is good to do in this point!
this.mockMvc.perform(get("/customers")).andExpect(status().isOk())
.andExpect(content().string(containsString("054b145c-ddbc-4136-a2bd-7bf45ed1bef7")))
.andExpect(content().string(containsString("38124691-9643-4f10-90a0-d980bca0b27d")));
}
@Test
void getCustomer() throws Exception {
this.mockMvc.perform(get("/customers/054b145c-ddbc-4136-a2bd-7bf45ed1bef7")).andExpect(status().isOk())
.andExpect(content().string(containsString("054b145c-ddbc-4136-a2bd-7bf45ed1bef7")));
}
@Test
void getCustomer_notFound() throws Exception {
this.mockMvc.perform(get("/customers/2b31469c-da3d-469f-9900-d00b5e4e352f")).andExpect(status().isNotFound());
}
@Test
void addCustomer() throws Exception {
Customer customer = new Customer("", "John", "Doe", "jdoe@test.com", "555-515-1234", "1234 Main St, Smallville KS 66083");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(customer);
this.mockMvc.perform(post("/customers").content(jsonString).contentType("application/json")).andExpect(status().isCreated())
.andExpect(content().string(containsString("jdoe@test.com")));
}
@Test
void addCustomer_exists() throws Exception {
Customer customer = new Customer("","Cally","Reynolds","penatibus.et@lectusa.com","(901) 166-8355","556 Lakewood Park, Bismarck, ND 58505");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(customer);
this.mockMvc.perform(post("/customers").content(jsonString).contentType("application/json")).andExpect(status().isConflict());
}
@Test
void updateCustomer() throws Exception {
Customer customer = new Customer("c04ca077-8c40-4437-b77a-41f510f3f185","Jack","Bower","quam.quis.diam@facilisisfacilisis.org","(831) 996-1240","2 Rockefeller Avenue, Waco, TX 76796");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(customer);
this.mockMvc.perform(put("/customers/c04ca077-8c40-4437-b77a-41f510f3f185").content(jsonString).contentType("application/json")).andExpect(status().isOk())
.andExpect(content().string(containsString("Jack")))
.andExpect(content().string(containsString("Bower")));
}
@Test
void updateCustomer_badRequest() throws Exception {
Customer customer = new Customer("c04ca077-8c40-4437-b77a-41f510f3f185","Jack","Bower","quam.quis.diam@facilisisfacilisis.org","(831) 996-1240","2 Rockefeller Avenue, Waco, TX 76796");
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(customer);
this.mockMvc.perform(put("/customers/2b31469c-da3d-469f-9900-d00b5e4e352f").content(jsonString).contentType("application/json")).andExpect(status().isBadRequest());
}
@Test
void deleteCustomer() throws Exception {
this.mockMvc.perform(delete("/customers/3b6c3ecc-fad7-49db-a14a-f396ed866e50")).andExpect(status().isResetContent());
}
}
- Observability = use {logging, metrics, tracing} in order to maintain an application
- Centralized logging when distributed systems
- How change log level within Spring Boot app to troubleshoot an issue without restarting your application: -> by using logback autoscan functionality, or with Spring cloud config library or with Spring Boot actuator and Spring admin
- Wavefront: easily integrates w/ Spring Boot
- Distributed monitoring: use Spring Boot actuator & micrometer
- Spring cloud sleuth = Spring library used to implement distributed tracing
- ALL
- DEBUG
- INFO
- WARN
- ERROR
- FATAL
- OFF
- TRACE
log.setLevel(level.CHOICE)
: CHOICE
& lower levels print
- Logger object: responsible for capturing logging info. They are stored in a namespace hierarchy
- Layout object: provides objects which are used to format logging info in different styles (support to apprender objects before publishin logging ingormation). They play an important role: publish info in a humanly readable way
- Apprender object: lower level: responsible for publishing logging info to various destinations
Java Program:
+--------------+
| Level obj ** | +-------------+
+--------------+ | Destination |
+-------------+
+----------------+ Λ
| log manager ** | |
+----------------+ | info is logged
|
+--------------+ +-----------------+
logging info -> | logger obj * | -> | Apprender Obj * |
+--------------+ +-----------------+
Λ
+---------------+ |
| Filter obj ** | | Formatted info
+---------------+ |
+--------------+
+-----------------+ | Layout Obj * |
| Obj renderer ** | +--------------+
+-----------------+
* core objects
** support objects
- Add this to maven file:
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>datasource-proxy-spring-boot-starter</artifactId>
<version>${ds-proxy-spring-boot-starter.version}</version>
</dependency>
- application properties:
logging.level.net.ttddyy.dsproxy.listener=debug
These will log SQL statements like so:
Name:dataSource, Connection:5, Time:1, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["
SELECT
u.id AS id1_7_,
u.email AS email5_7_,
u.external_id AS external6_7_,
u.first_name AS first_na7_7_,
u.last_name AS last_nam9_7_,
FROM users u
WHERE u.email = ?
"]
Params:[(
john.doe@acme.com
)]
Pointcuts determine join points of interest and thus enable us to control when advice runs.
Further reading: