Monday, June 22, 2020

How to create HA Kubernetes cluster on AWS using Kops?

Objectives

The main goal is to create high available kubernetes (k8s) cluster spread across zones which belongs to one AWS region and this example presents real use case which could be used by programmers. I used very helpfully open-source solution - Kops. This tool configure networking, route tables, VPC,  auto-scaling components and DNS in private zone. In addition the Master and Worker Nodes are created order to our requirements.




The Solution

The solution is spited in few steps. It was necessary to previously create additional manage server VPC etc. and finally create the k8s cluster using Kops.

1. Create Security Group with access rules. That security groups allows to connect to. 





















































2. Crate EC2 server. Find service EC2 -> create new Instance -> Choose Image (AMI) -> Add tags, keys (to login) and previously created security group. Finally we should have similar summary screen




























3. Install kubectl on Manage Server (point no 2). The description for this point is: https://kubernetes.io/docs/tasks/tools/install-kubectl/ . It is necessary to login to this server using i.e. Putty and then run commands:
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
kubectl version --client

4. Install Kops CLI on Manage Server. This is main tool for creating cluster and description is available on: https://aws.amazon.com/blogs/compute/kubernetes-clusters-aws-kops/ and https://kubernetes.io/docs/setup/production-environment/tools/kops/ Run the below commands:

curl -LO https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64
chmod +x kops-linux-amd64
mv kops-linux-amd64 /usr/local/bin/kops
5. Install Install AWS CLI on Manage Server. The description is available on: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html. Execute commands:

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install -i /usr/local/aws-cli -b /usr/local/bin
aws --version

6. Create role and attach permissions policies:
AmazonEC2FullAccess
AmazonRoute53FullAccess
AmazonS3FullAccess
IAMFullAccess
AmazonVPCFullAccess

To create role you have to select Identity Access Management (IAM) service and choose Roles

















































7. Configure manage server using command:

aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: eu-central-1
Default output format [None]:
Choose your selected region.

8. Create an S3 bucket for the Kubernetes state store

aws s3 mb s3://s3-artsci-bucket
export KOPS_STATE_STORE=s3://s3-artsci-bucket
9. Create private hosted zone







































10. Create key for cluster
ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):***
Enter same passphrase again:***
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:***/BBmlik root@ip-10-31-12-10
The key's randomart image is:
+---[RSA 2048]----+
|        o=.+S=o+.|
|        .+o E.@. |
|         .+  * = |
|         * .  o +|
|        S + o  =.|
|         o +  o *|
|          o .* O |
|         +  . * +|
|        . o. .1o |
+----[SHA256]-----+

11. Create k8s cluster

Create definition:
kops create cluster --cloud=aws --name=cluster.artsci.pl --zones=eu-central-1a,eu-central-1b,,eu-central-1c --node-count=3 --master-count=3 --state=s3://s3-artsci-bucket --dns-zone=cluster.artsci.pl --dns=private
You should receive outcome:
...
...
Suggestions:
 * list clusters with: kops get cluster
 * edit this cluster with: kops edit cluster cluster.artsci.pl
 * edit your node instance group: kops edit ig --name=cluster.artsci.pl nodes
 * edit your master instance group: kops edit ig --name=cluster.artsci.pl master-eu-central-1a 

In addition it is available a few commands, for example change server size:

kops edit ig --name=cluster.artsci.pl nodes --state=s3://s3-artsci-bucket

And finally apply all changes :
kops update cluster cluster.artsci.pl --state=s3://s3-artsci-bucket --yes

The results

Our cluster should be available after a few minutes. We can check status of this cluster:

kops validate cluster --state=s3://s3-artsci-bucket
Using cluster from kubectl context: cluster.artsci.pl
Validating cluster cluster.artsci.pl
INSTANCE GROUPS
NAME                    ROLE    MACHINETYPE     MIN     MAX     SUBNETS
master-eu-central-1a    Master  t2.micro        1       1       eu-central-1a
master-eu-central-1b    Master  t3.medium       1       1       eu-central-1b
master-eu-central-1c    Master  t3.medium       1       1       eu-central-1c
nodes                   Node    t2.micro        3       3       eu-central-1a,eu-central-1b,eu-central-1c
...
...

Kops created networks:
















EC2 instances:












Security Groups for k8s cluster:

















VPC for cluster





Monday, June 15, 2020

AWS Lambda - python and java

AWS Lambda

Overview

AWS Lambda is a serverless, automatically scaling component for handling requests from various sources. This computing service is very helpful to prepare data, check changed element on S3, etc.It also could work as an element to handle requests from web services. Below is prepared very simple solution which contains two AWS Lambda functions. First one (created in python) is responsible for logic. The second one (created in java) is called for calculate array's average value.  






The solution

AWS Lambda in python

I prepared python code with the logic. Function calculates internally sum of array's elements. If average function is selected then next one AWS Lambda is called. 


import logging
import boto3
import json
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('lambda')
def lambda_handler(event, context):
    logger.info("FrontFunction got event{}".format(event))
    operation = event['operation']
    listOfItems = event['listOfItems']
 
    arrayStr = []
 
    if (operation == 'avg'):
        for i in listOfItems:
            arrayStr.append(i)
        response = client.invoke(
            FunctionName="Java8Tutorial",
            InvocationType='RequestResponse',
            Payload=json.dumps({"queryParameters": {"listOfItems":arrayStr}})
        )
        response = json.loads(response['Payload'].read().decode("utf-8"))
    elif (operation == 'sum'):
        out = 0
        for i in listOfItems:
            logger.info(i)
            out += int(i)
        response = out 
    else:
        response = 'Not defined'
 
    logger.info(response)
    return response

GatewayAPI contains reference to this Lambda function.  

































CloudWatch component for monitoring is integrated with function's dashboard. 





























On the top toolbar is also available button to configure function throttling.   

GatewayAPI

Gateway API couples HTTP requests (methods: GET, PUT, POST, DELETE,.. )  with Lambda functions. My API is not protected because it is only for testing but it is a good practice to protect communication. Below we can see the HTTP mapping to the Lambda Function.  
























AWS Lambda in java

Second lambda is created in Java. Java works generally slower then Python and "coldstart" consume more time. We can see simple code for parse JSON and calculate average value.   


package com.amazonaws.lambda.java8tutorial;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.OptionalDouble;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.json.simple.parser.JSONParser;
public class LambdaFunctionHandler implements RequestStreamHandler {
    @Override
    @SuppressWarnings("unchecked")
    public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
            LambdaLogger logger = context.getLogger();
        logger.log("Start ARTSCI: Java Lambda handler ");             
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        JSONObject outJson = new JSONObject();     
        JSONArray arrayItems = new JSONArray();
        String outputString = "Array's avg value is ";
        try {
        JSONParser parser = new JSONParser();
            JSONObject event = (JSONObject)parser.parse(reader);
            if (event.get("queryParameters") != null) {
                JSONObject qps = (JSONObject)event.get("queryParameters");
                if ( qps.get("listOfItems") != null) {                                arrayItems = (JSONArray) qps.get("listOfItems");
                OptionalDouble avg = arrayItems.stream().mapToInt(str -> Integer.parseInt((String) str)).average();
                    outputString += avg.getAsDouble();
                }
            }
         
         
            JSONObject responseBody = new JSONObject();
            responseBody.put("message", outputString);
            outJson.put("body", responseBody);
         
        } catch(ParseException pex) {
        outJson.put("statusCode", "400");
        outJson.put("exception", pex);
        }
     
     
        logger.log(outJson.toJSONString());
        OutputStreamWriter writer = new OutputStreamWriter(output, "UTF-8");
        writer.write(outJson.toJSONString());
        writer.close();
    }
}

Very important is to change permissions to allow one Lambda function call another one Lambda function.  


















The Results

At the end it is necessary to test all functionality.

Calculation of AVG
C:\Users\Artur>curl --location --request GET https://[unique].execute-api.eu-central-1.amazonaws.com/dev?name=artsci --header "Content-Type: application/json" --data-raw "{\"operation\": \"avg\",\"listOfItems\": [\"1\",\"2\",\"3\",\"4\",\"5\",\"6\"]}"
{"body": {"message": "Array's avg value is 3.5"}}

Calculation of SUM
C:\Users\Artur>curl --location --request GET https://[unique].execute-api.eu-central-1.amazonaws.com/dev?name=artsci --header "Content-Type: application/json" --data-raw "{\"operation\": \"sum\",\"listOfItems\": [\"1\",\"2\",\"3\",\"4\",\"5\",\"6\"]}"
21








Tuesday, June 9, 2020

AWS - how to create admin user?

AWS

Objectives

Today I'm going to describe how to create a new admin user in the biggest cloud platform - AWS. AWS currently contains 212 available services which can be used to build complete environment. Admin user is necessary to manage all environments and services but that role is also limited and there is no default permission to some high level services like i.e. billing 

Admin user

At the beginning let's find IAM (Identity and Access Management) service and create new group.




















Press button "Create new group"



Type your group name

















Select appropriate policy















And finally create new group














So, the Administrators group has just been created. Let's create "admin" user and add it to previously created group.  

Press button "Add user"














Type user name and select access types


























Select group with permissions 


















Add tags















User is almost created 




























That's it. User is active and access key is generated 




























Amazon CLI - Command Line Interface

CLI is very useful tool which could be used by administrators or ansible playbooks to create infrastructure on AWS.


After download and install we can check it.


aws configure
AWS Access Key ID [None]: [access key]
AWS Secret Access Key [None]: [secret key]
Default region name [None]: eu-central-1
Default output format [None]: UTF-8


Base on configuration we can create KeyPair for testing


aws ec2 create-key-pair --key-name ArtsciKeyPair --query 'KeyMaterial' --output text > ArtsciKeyPair.pem


And using AWS console we can observe the results :)








Thursday, May 28, 2020

Token JWT and Spring Boot

Objectives

Today I would like to extend my previous post (https://java-architect.blogspot.com/2020/05/spring-boot-rest-api-with-security.html) and add protection API based on JWT Token. At the beginning I would like to briefly describe the idea of tokens. Below is added BPMN process flow which shows the end-to-end path (this is a simple process flow without filters, authentication controllers or authentication managers)












The token structure
Token consists of three elements:
  • Header (algorithm and type) - {"alg":"HS256"}
  • Payload  - {"sub":"user","exp":1590655192}
  • Signature (defined secret was used to create signature)


Application

I created application based on my previous post (https://java-architect.blogspot.com/2020/05/spring-boot-rest-api-with-security.html).  The main goal is to create JWT token to protect communication between server and clients. 




I selected files which I added or replaced. Let's examine the application's code. 


pom.xml

  <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>  
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-openid</artifactId>  
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>  
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>  
</dependency>


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>



UserDTO:

@Data
public class UserDTO {
    private String username;
    private String password;
}



SecurityConfig.class


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
        public static final String prefix = "Bearer";
public static final String header = "Authorization";
        public static final String secret = Base64.getEncoder().encodeToString("artsci".getBytes());;
        public static final Long expir = new Long(3600000);

@SuppressWarnings("deprecation")
@Bean
    public UserDetailsService userDetailsService() {
        User.UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("user").password("user").roles("USER").build());
        manager.createUser(users.username("admin").password("admin").roles("USER", "ADMIN").build());
        return manager;
    }

   @Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(userDetailsService());
}
   
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        http.csrf().disable()   
        .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/login").permitAll()
        .anyRequest().authenticated()
        .and().addFilterBefore(new LoginFilter("/login", authenticationManager()),   
                 UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(new AuthJwtFilter(), UsernamePasswordAuthenticationFilter.class);
        http.headers().cacheControl();
    }


LoginFilter.class

public class LoginFilter extends AbstractAuthenticationProcessingFilter {
    public LoginFilter(String url, AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authenticationManager);
   }

   @Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse
           response)
throws AuthenticationException, IOException, ServletException {
    UserDTO user = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
     UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                                                                                                 user.getUsername(),
                                                                                                 user.getPassword(),
                                                                                                 Collections.emptyList());

     return getAuthenticationManager().authenticate(token);
}

   @Override

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
          FilterChain chain,
Authentication authResult) throws IOException, ServletException {

     String token = Jwts.builder()
            .setSubject(authResult.getName())         
            .signWith(SignatureAlgorithm.HS256, secret)
            .setExpiration(new Date(System.currentTimeMillis() + expir))
            .compact();
     response.addHeader(header, prefix + " " + token);    
     response.setContentType("application/json");
     response.setCharacterEncoding("UTF-8");
     response.getWriter().write("{\"" + header + "\":\"" + prefix + " " + token + "\"}");     
}
}

AuthJwtFilter.class

public class AuthJwtFilter extends GenericFilterBean{
@Override
public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain) throws IOException, ServletException {
Authentication auth = null;
         if(!((HttpServletRequest) request).getHeader(header).isEmpty()) {
String claim = ((HttpServletRequest)request).getHeader(header) 
                             .replace(prefix,"").substring(1);
String user = Jwts.parser().setSigningKey(secret).parseClaimsJws(claim)
                        .getBody().getSubject();
auth = new UsernamePasswordAuthenticationToken(user, null,                                      
                             Collections.emptyList());

}
       
         SecurityContextHolder.getContext().setAuthentication(auth);
         chain.doFilter(request,response);
}
}

The results

So, our application should work correctly. Let's try to call protected API. At the beginning it is necessary to to generate token. To achieve this goal I attempt to use URL:  "/login" and pass my credential. Below is example of CURL's request.


curl --location --request POST http://localhost:8080/login --header "Content-Type: application/json" --data-raw "{\"username\":\"user\",\"password\":\"user\"}"
{"Authorization":"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNTkwNjU1MTkyfQ.lKZMYWVKd9OZu2nOwMjRxXjewQ-zYKQWWB4wIp1Zhi8"}

Fantastic, JWT Token was generated. Let's try to call "/regions" service using previously generated token.


curl -H "Authorization":"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNTkwNjU1MTkyfQ.lKZMYWVKd9OZu2nOwMjRxXjewQ-zYKQWWB4wIp1Zhi8" --request GET http://localhost:8080/regions/
[{"regionId":1,"name":"Europe"},{"regionId":2,"name":"Americas"},{"regionId":3,"name":"Asia"},{"regionId":4,"name":"Middle East and Africa"}]

It seems that the solution works exactly as I expected :)

Wednesday, May 27, 2020

Spring Security with REST API

Spring Security with REST API 

Objectives

Today I would like to dive into security topic in Spring boot application. I base on my previous post (https://java-architect.blogspot.com/2020/05/spring-boot-rest-api-with-jpa.html) with the sources. I'm going to change and add protection to the API. There are two important aspect:

  • Authentication - process of identifying the user who calls the API
  • Authorization -  process of checking user's permission to call resources

The Authentication process can base on plain text password, digest method, JWT (Java WEB Token), OAuth, SAML or other method to identify user. Besides previously mentioned methods, very often applications are protected by certificates.


The simplest way to authenticate 

So, due the topic, the simplest way to enable user authentication is to add the appropriate configuration.



























For simplification I defined users and roles in memory. Of course in production environment that data should be fetched form LDAP, Data Base or other identity server.. 


package com.main.artsci.configutarion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
       @SuppressWarnings("deprecation")
       @Bean
       public UserDetailsService userDetailsService() {
              User.UserBuilder users = User.withDefaultPasswordEncoder();
              InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
              manager.createUser(users.username("user").password("user").roles("USER").build());
              manager.createUser(users.username("admin").password("admin").roles("USER", "ADMIN").build());
               return manager;
        }
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/regions/complex/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().httpBasic();
}
}

In "userDetailsService()" method I defined all necessary users for this test case. In "configure()" method I enabled authentication for every request and all users can have access to all resources except resource defined by path "/regions/complex/" where access has only administrator.

In addition I created SecurityWebApplicationInitializer 

public class SecurityWebApplicationInitializer
   extends AbstractSecurityWebApplicationInitializer {
}

Let's see the pom's changes. I added security libraries:

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
     </dependency>
     <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
      </dependency>
<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>  
</dependency>
<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-openid</artifactId>  
</dependency>
<dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-ldap</artifactId>  
</dependency>

  
Finally it is necessary to checks is requested resources are protected in a proper way.  

Let's see the results after fetching resource available to everyone who pass true the authentication process.
curl --user user:user http://localhost:8080/regions
[{"regionId":1,"name":"Europe"},{"regionId":2,"name":"Americas"},{"regionId":3,"name":"Asia"},{"regionId":4,"name":"Middle East and Africa"}]
The results are exactly we expect to achieve. Next what does happen if we try to fetch protected data? 
curl --user user:user http://localhost:8080/regions/complex/1
{"timestamp":"2020-05-27T07:20:00.487+0000","status":403,"error":"Forbidden", "message":"Forbidden","path":"/regions/complex/1"} 

The error occurs. There is no permission. Let's change user to admin.

curl --user admin:admin http://localhost:8080/regions/complex/1
{"regionId":1,"name":"Europe","countries":[{"countryId":"BE","name":"Belgium"},{"countryId":"CH","name":"Switzerland"},{"countryId":"DE","name":"Germany"},{"countryId":"DK","name":"Denmark"},{"countryId":"FR","name":"France"},{"countryId":"IT","name":"Italy"},{"countryId":"NL","name":"Netherlands"},{"countryId":"UK","name":"United Kingdom"}]}
And everything is correct :)

Monday, May 25, 2020

Spring boot REST API with JPA

Spring boot REST API with JPA

Objectives 

The main goal is to create small application which could evolve to microservice. The application gather data from Oracle database then expose  that data by REST service with JSON objects. There were used a few additional libraries:

  • HikariCP - additional library to manage connection pool to database    
  • Swagger - libraries to create documentation in OpenAPI standard
  • Lombok - library to reduce boilerplate code in java classes


Below is the structure of application:














































The application's code 

The model

Based on Oracle database image which was created in my previous post (https://java-architect.blogspot.com/2020/05/oracle-plsql-part-1.html) I build selected java Entities. Lombok library was very helpful to significantly reduce boilerplate java code. It was necessary to use annotation "@JsonManagedReference" and "@JsonBackReference" for protecting against getting an errors.


The Region class:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@Entity
@Table(name = "REGIONS")
public class Region  {
    @Id
    @Column(name="REGION_ID")
    private Long regionId;
 
    @NotNull
    @Column(name = "REGION_NAME")
    private String name;
 
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "region")
    @JsonManagedReference
    private List<Country> countries;
 
    public Region(){
        }     
}

The Country class:
@Getter
@Setter
@ToString
@EqualsAndHashCode
@Entity
@Table(name = "COUNTRIES")
public class Country {
    @Id
    @Column(name="COUNTRY_ID")
    private String countryId;
 
    @Column(name = "COUNTRY_NAME")
    private String name; 
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "REGION_ID", nullable = false)
    @JsonBackReference
    private Region region;
 
    public Country() {
        }
}

The repository

@Repository
public interface RegionRepository extends CrudRepository<Region, Long> { Optional<Region> findById(Long id);
}

@Repository
public interface CountryRepository extends CrudRepository<Country, String>{
    Optional<Country> findById(String countryId);   

    @Query("select c from Country c where c.name = :name")
           Stream<Country> findByNameReturnStream(@Param("name") String name);
}


The service

@Transactional
@Service("regionService")
public class RegionServiceImpl implements RegionService{
@Autowired
CountryRepository countryRepository;

@Autowired
RegionRepository regionRepository;

public List<RegionDTO> getRegions() {
                List<RegionDTO> list = new ArrayList<RegionDTO>();

for(Region r : regionRepository.findAll()) {
list.add(new RegionDTO(r));
}
return list;
}

        public RegionDTO getRegionByIdv1(Long id) {
return new RegionDTO(regionRepository.findById(id).get());

}

        public RegionDTOv2 getRegionByIdv2(Long id) {

Region region = regionRepository.findById(id).get();
region.getCountries();
return  new RegionDTOv2(region);
}
}


The RestController

@RestController
@EnableSwagger2
public class BaseController  {
@Autowired
@Qualifier("regionService")
RegionService regionService;

       @RequestMapping(method = RequestMethod.GET, value = "/regions")
        public ResponseEntity<List<RegionDTO>> regions() {           
               return new ResponseEntity<List<RegionDTO>>(regionService.getRegions(), HttpStatus.OK);

        }
 
       @RequestMapping(value = "/regions/simple/{regionId}", method = RequestMethod.GET)
  public ResponseEntity<RegionDTO> getRegionById(@PathVariable("regionId") long regionId) {
RegionDTO regionDTO = regionService.getRegionByIdv1(regionId);
return new ResponseEntity<RegionDTO>(regionDTO, HttpStatus.OK);
}
 
        @RequestMapping(value = "/regions/complex/{regionId}", method = RequestMethod.GET)
public ResponseEntity<RegionDTOv2> getRegionByIdV2(@PathVariable("regionId") long             regionId) {
    RegionDTOv2 regionDTOv2 = regionService.getRegionByIdv2(regionId);
    return new ResponseEntity<RegionDTOv2>(regionDTOv2, HttpStatus.OK);
}
}


Application.properties

#spring boot configuration for Oracle
spring.datasource.url=jdbc:oracle:thin:@localhost:49161:xe
spring.datasource.username=hr
spring.datasource.password=hr
spring.datasource.driver-class-oracle.jdbc.driver.OracleDriver

#hibernate dialect config
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
spring.jackson.serialization.fail-on-empty-beans=false
spring.jpa.show-sql=true
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1

# HikariCP settings
spring.datasource.hikari.*
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.maximum-pool-size=10



The results

Specification

The results can be visualized in web browser. Let's see the API's documentation.





































The output of method "/regions"

The base output of method "/regions" has:

JSON representation:























Data representation:























And the Headers:
































The output of method "/regions/complex/{regionId}"

























What else

The CRUD operations are usually mapped to HTTP method in REST web services like below:

  • CREATE -> POST
  • READ -> GET
  • UPDATE -> PUT
  • DELETE -> DELETE