Monday, April 26, 2021

Still using Java: Nginx infront of Quarkus Services

 

Today's goal:

- put a load balancer in front of our services.
- create a dummy domain name by modifying the hosts file.
- access the service using the dummy domain name.

First, let's check out all the services from github.

Download nginx from

Then open up all the services:

We try to start them up.

And we face our first problem.


We have 5 services trying to run on port 5005 for Quarkus debugging and port 8080 for the web application. To solve this, we will run our application on different ports. 

Setting port used in application.properties 

quarkus.http.port=9090

For Quarkus debugging port we defined it on the maven command.

mvn compile quarkus:dev -Ddebug=5006

Once the ports are set, we start all the services again.


This is the setup for now.


Next, we are going to edit the hosts file. 

127.0.0.1   mypetshop.local

We add this entry to let mypetshop.local points to the current host machine.

After the hosts file is edited, we can test it with Postman.


It will give us back a result as if we are hitting localhost.

Now we create a nginx config file, saved as mypetshop.conf

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    upstream auth_service {
        server localhost:8080;
    }
    upstream cart_service {
        server localhost:8090;
    }
    upstream inventory_service {
        server localhost:8100;
    }
    upstream payment_service {
        server localhost:8110;
    }
    upstream transaction_service {
        server localhost:8120;
    }

    server {
        listen 80;
        location /auth {
            proxy_pass http://auth_service/auth;
        }
        location /cart {
            proxy_pass http://cart_service/cart;
        }
        location /inventory {
            proxy_pass http://inventory_service/inventory;
        }
        location /payment {
            proxy_pass http://payment_service/payment;
        }
        location /transaction {
            proxy_pass http://transaction_service/transaction;
        }
    }
}

then we start nginx with the config file.

nginx -c mypershop.conf

To test the outcome, in Postman we create a new Environment


  We create a new "host" variable with a value like below.  

Now open the Auth Service collection and change the List Users request.


We change the 'localhost:8080' into using the variable '{{host}}'.

Make sure to select the environment that we have created just now.


When we hit the request, we should get a 200 response.


Ok. now we try something more interesting. To demonstrate how nginx do load balancing:

Change the GET cart function to print the count in the console.

    static int count = 0;

    @GET
    public Cart getCart(@HeaderParam("X-User-Id") Long userId) {
        Cart cart = findCart(userId);
        logger.info(" Request count = {}", ++count);
        return cart;
    }
    

Run H2 Database in Server mode.


Download the h2 jar file from h2 website. In a terminal, start the server using the command above. 

For the application properties


    # change database url to connect in Server mode.
    quarkus.datasource.jdbc.url = jdbc:h2:tcp://localhost/~/cart-database
    
    # run an instance in port 8090
    quarkus.http.port=8090
    
    # run an instance in port 8091
    quarkus.http.port=8091
    
    

Update the nginx configuration.


Restart nginx and hit the GET cart request like crazy.


You should see the count printed alternately from the two instances.

Wednesday, April 21, 2021

Still using Java: Secured REST Resource with Quarkus Form Based Authentication

Quarkus comes with a lot of authentication mechanisms. Today we are going to implement a simple one with Form-Based Authentication. Quarkus has good documentation but the example for authentication is all over the place. We will create one here just for future reference.

You can visit the documentation here.

A quick description of what we want to do is:

- we have some REST endpoints
- we want to secure the endpoints
- with minimal effort
- username and password saved in the database

These are the basic requirement. 

To begin, check out my previous source code from https://github.com/devilkazuya99/inventory-service

It should have 5 existing endpoints in InventoryResource.java. 

The first thing to do is to enable form-based authentication in the application.properties file.


This will unlock some forbidden dark arts into your system.

Now we need to write some entity class to hold our user's credentials.

        @Entity
        @UserDefinition
        public class AppUser extends PanacheEntity {
        
            @Username
            public String username;
            @Password
            public String password;
            @ManyToMany(cascade = CascadeType.ALL)
            @Roles
            public List<UserRole> roles = new ArrayList<>();;
            
            public static void add(String username, String password, String role) { 
                AppUser user = new AppUser();
                user.username = username;
                user.password = BcryptUtil.bcryptHash(password);
                UserRole userRole = new UserRole();
                userRole.role = role;
                user.roles.add(userRole);
                user.persist();
            }
                
        }
        @Entity
        public class UserRole extends PanacheEntity {
            
            @ManyToMany(mappedBy = "roles")
            public List<AppUser> users;
                
            @RolesValue
            public String role;
        }

Our AppUser has a username and password. It also can have a list of UserRoles.


In listCategory() endpoint in InventoryResource.java, add a @RolesAllowed("user") annotation to the function.


Lastly, add 2 static files, one for login and one for the error page.

The error page is just a dummy page with static content. We will explain more on the login page. 


    <div class="container">
        <section id="content">
            <form action="/j_security_check" method="POST">
                <h1>Login Form</h1>
                <div>
                    <input type="text" placeholder="Username" required="" id="username" name="j_username" />
                </div>
                <div>
                    <input type="password" placeholder="Password" required="" id="password" name="j_password" />
                </div>
                <div>
                    <input type="submit" value="Log in" />
                    <a href="#">Lost your password?</a>
                    <a href="#">Register</a>
                </div>
            </form><!-- form -->

        </section><!-- content -->
    </div><!-- container -->

In short, the login form will POST to '/j_security_check', which is provided by Quarkus dark art. The default username is 'j_username' and the password is 'j_password'. That's all it is.

These are default values that can be modified. For more details see here.

Now, starts the application with this command (that you need to remember for the rest of your life).

mvn compile quarkus:dev

Using a web browser we first browse the product page: http://localhost:8080/inventory/product

We will get an empty array as the response. 

[]

Now we browse the catalog page: http://localhost:8080/inventory/category

This time we got redirected to the login page.

The Quarkus dark art is working so far. We annotated the endpoint previously to only allows user with role "user" to access. It is looking good. 

We forgot one important thing. We don't have any user to login. Let's go back and create some.

We create a Startup class. (you can give it any name)

@Singleton
public class Startup {
	
    @Transactional
    public void loadUsers(@Observes StartupEvent evt) {
        // reset and load all test users
        AppUser.deleteAll();
        AppUser.add("admin", "admin", "admin");
        AppUser.add("user", "user", "user");
    }
	
}

Pay attention to the loadUsers function. The function has a @Observes annotation, which will listen to the Quarkus StartEvent. So every time Quarkus starts up, this function will be triggered. When it happened, we cleared the table, and add 2 dummy users to it.

Now, we go back to the category page, http://localhost:8080/inventory/category, key in the username and password, Voilà, we can see the empty JSON array.

[]

Monday, April 19, 2021

Still using Java: Working with the project code

This is a short post about how to check out the project from Github and work in an IDE. I'm using Eclipse as my IDE. Similar concepts and configurations should be applicable to other IDE. 




1st checkout the code from my Github repository.



On the [Package Explorer], right-clicks and click 'Import...'


Choose 'Existing Maven Project'.

Click 'Finish' to import.


When you import, the IDE will start to build the project. Make sure it is using Java 11 as JRE. The Jave version is defined in the pom.xml. 

After import, you should be able to run the project. Right-click on the project > Run As > Maven build...

 


Set the Goals to: compile quarkus:dev

Make sure the Maven Runtime version is 3.6.2+


Select the JRE tab (this is important). Make sure you use Java 11 from GraalVM. If you don't have it, go download a copy now. 


Once you hit the 'Run' button, wait for the message to be shown on the console. 

Now you can start coding. Yes. You hear me right. Start coding. No need to restart the application anymore. Add a new function? Add new class? Add a new database entity? Just hit the endpoint after the changes. Quarkus will reload your app and in a very fast manner. 

Sunday, April 18, 2021

Still using Java: Cart, Payment, and Transaction Services

In order to get to the fun part, where we try to integrate all the services, I just quickly draft some requirement and roll them out. We will make changes to it accordingly ni the future.


 


We will create some endpoints for these services, persist the data into a database, that's it.

Create 3 projects using maven command


mvn io.quarkus:quarkus-maven-plugin:1.13.1.Final:create -DprojectGroupId=blog.technodeck -DprojectArtifactId=cart-service -Dverison=0.0.1-SNAPSHOT -Dextensions="resteasy-jackson,jdbc-h2,hibernate-orm-panache"
mvn io.quarkus:quarkus-maven-plugin:1.13.1.Final:create -DprojectGroupId=blog.technodeck -DprojectArtifactId=payment-service -Dverison=0.0.1-SNAPSHOT -Dextensions="resteasy-jackson,jdbc-h2,hibernate-orm-panache"
mvn io.quarkus:quarkus-maven-plugin:1.13.1.Final:create -DprojectGroupId=blog.technodeck -DprojectArtifactId=transaction-service -Dverison=0.0.1-SNAPSHOT -Dextensions="resteasy-jackson,jdbc-h2,hibernate-orm-panache"

then configure the application.properties accordingly.

Cart Service

# configure your datasource
quarkus.datasource.db-kind = h2
quarkus.datasource.username = sa
quarkus.datasource.password = sa
quarkus.datasource.jdbc.url = jdbc:h2:~/cart-database
# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation = update

Payment Service

# configure your datasource
quarkus.datasource.db-kind = h2
quarkus.datasource.username = sa
quarkus.datasource.password = sa
quarkus.datasource.jdbc.url = jdbc:h2:~/payment-database
# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation = update

Transaction Service

# configure your datasource
quarkus.datasource.db-kind = h2
quarkus.datasource.username = sa
quarkus.datasource.password = sa
quarkus.datasource.jdbc.url = jdbc:h2:~/transaction-database
# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation = update

Cart Service Endpoint

We create simple entities.

@Entity
public class Cart extends PanacheEntity {

    public Long userId;
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    public List<CartItem> items;
    public ZonedDateTime createdDate;

}
@Entity
public class CartItem extends PanacheEntity {

    public Long productId;
    public String name;
    public Float price;
    public int quantity;

}

Then the resource class

@Path("/cart")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CartResource {...}

The resource class can do basic CRUD operation. I won't show the full detail, but there are some interesting API that I found it useful and interesting.

Public functions that mapped to @GET, @PPOST, @PUT, and @DELETE


    Cart cart = (Cart) Cart.find("userId", userId)
                               .firstResultOptional()
                               .orElse(new Cart());

I like this firstResultOptional function from the PanacheQuery class. It returns an Optional which is very handy. Spring Data has this feature too.

Payment Service

Payment Service has a Wallet.

@Entity
public class Wallet extends PanacheEntity {
	
	@NotNull
	@Column(unique = true)
    public Long userId;
	@NotNull
    public Float balance;
	
	@NotBlank
	public String description;
	
}

It also has a few data classes.

public class CardInfo {

	@NotBlank
	public String cardNumber;
	@NotBlank
	public String nameOnCard;
	@NotNull
	public Integer expMonth;
	@NotNull
	public Integer expYear;
	@NotNull
	public Integer ccv;
	
}

//

public class PaymentDetail {

	@NotNull
	public Long walletId;
	@NotNull
	public Float amount;
	public String transactionId;
	public ZonedDateTime createdDate;
	public ZonedDateTime updatedDate;
	public String status;
	
	public PaymentDetail() {}
	
	public PaymentDetail(@NotNull Long walletId, @NotNull Float amount, String transactionId, ZonedDateTime createdDate,
	        ZonedDateTime updatedDate, String status) {
		super();
		this.walletId = walletId;
		this.amount = amount;
		this.transactionId = transactionId;
		this.createdDate = createdDate;
		this.updatedDate = updatedDate;
		this.status = status;
	}
	
}

//

public class TopupInfo {

	@NotNull
	public Long walletId;
	@NotNull
	public Float amount;
	@NotNull
	public CardInfo card;
	
}

In this service, we are experimenting with the Quarkus Validation extension.

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-validator</artifactId>
        </dependency>

By including this dependency, it allows us to validate the request body by simply adding a @Valid annotation in the parameter. See the createWallet() function below. 


Here is the PaymentResource


Let's test the validation by creating a wallet without a required field.


You will get a build-in report. How cool is that?


Lastly Transaction Service

The Entity classes.

@Entity
public class ShippingInfo extends PanacheEntity{

    @NotBlank
    public String recieverName;
    public String company;
    @NotBlank
    public String addressLine1;
    public String addressLine2;
    public String addressLine3;
    public String addressLine4;
    public Integer postcode;
    @NotBlank
    @Column(length = 50)
    public String state;
    @NotBlank
    @Column(length = 50)
    public String country;
    
}
// ---------------------------------
@Entity
public class PurchaseTransaction extends PanacheEntity {

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @NotNull
    @Valid
    public ShippingInfo shippingInfo;
    @NotNull
    @Column(nullable = false)
    public Long cartId;
    @NotNull
    @Column(nullable = false)
    public Long paymentId;
    @NotNull
    @Column(nullable = false)
    public Double amount;

    public ZonedDateTime createdDate;
    
    @PrePersist
    public void beforePersist() {
        createdDate = ZonedDateTime.now();
    }
}
// ---------------------------------
@Entity
public class WalletTransaction extends PanacheEntity {

    @NotNull
    @Column(nullable = false)
    public Long walletId;
    @NotNull
    @Column(nullable = false)
    public Long userId;
    @NotNull
    @Column(nullable = false)
    public Long txId;
    @NotNull
    @Column(nullable = false)
    public String txType;
    public Double credit;
    public Double debit;

    public ZonedDateTime createdDate;
    
    @PrePersist
    public void beforePersist() {
        createdDate = ZonedDateTime.now();
    }
    
}

The resource endpoint.

@Path("/tx")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TransactionResource {

    @Transactional
    @POST
    @Path("/purchase")
    public void purchaseTx(@Valid PurchaseTransaction purchaseTransaction) {
        purchaseTransaction.persist();
    }

    @GET
    @Path("/purchase")
    public List<PurchaseTransaction> getPurchaseTransaction() {
        return PurchaseTransaction.findAll(Sort.by("id").ascending()).list();
    }
    
    @Transactional
    @POST
    @Path("/wallet")
    public void walletTx(@Valid WalletTransaction walletTransaction) {
        walletTransaction.persist();   
    }
    
    @GET
    @Path("/wallet")
    public List<WalletTransaction> getWalletTransaction() {
        return WalletTransaction.findAll(Sort.by("id").ascending()).list();
    }
    
}

Making some POST requests



Friday, April 16, 2021

When Atomikos meets Sql2o

 Today we play with something we don't normally do in a live application. We are going to married Atomikos and Sql2o.

Atomikos is a lightweight transaction manager for Java that enables applications using distributed transactions to be self-contained.

Sql2o is a small Java library, that makes it easy to execute sql statements against your JDBC compliant database.

We normally use Atomikos to manage the transaction. Sql2o also has it's own way for transaction management. 

// Sql2o Transaction
String sql1 = "INSERT INTO SomeTable(id, value) VALUES (:id, :value)";
String sql2 = "UPDATE SomeOtherTable SET value = :val WHERE id = :id";

try (Connection con = sql2o.beginTransaction()) {
    con.createQuery(sql1).addParameter("id", idVariable1).addParameter("val", valueVariable1).executeUpdate();
    con.createQuery(sql2).addParameter("id", idVariable2).addParameter("val", valueVariable2).executeUpdate();
    con.commit();
} 

So if we mix the two together, what kind of chemistry they will create?

Thursday, April 15, 2021

Refactoring some code

Work on refactoring some javascript.

Before:


const MyFunction = function (key) {
                        if (key) {
                            switch (key) {
                                case "ABC":
                                    return "Info 1";
                                case "DEF":
                                    return "Info 2";
                                case "GHI":
                                    return "Info 3";
                                case "JKL":
                                    return "Info 4";
                                case "MNO":
                                    return "Info 5";
                            }
                        }
                        return "";
                    };

After


const MyFunction = function (key) {
                        const data = {
                                        "ABC": "Info 1",
                                        "DEF": "Info 2",
                                        "GHI": "Info 3",
                                        "JKL": "Info 4",
                                        "MNO": "Info 5"
                                     };
                        return data[key];
                    };

Sweet~!

Sunday, April 11, 2021

Still Using Java: Inventory Service

 Today we want to continue creating the Inventory Service with Quarkus. Here is a simple idea.








The item lifecycle (I made it up)












Still using Java: Authentication Service (Part 1)

Today we will be building an Authentication Service. Starts with a simple REST service. 


v1

Saturday, April 10, 2021

Still using Java: 1

Yes. The year is 2021, and I'm still using Java. Recently we have Quarkus came out and things got even more interesting. It is like Java on steroids. I'll leave the experts to tell you how good/bad/whatever Quarkus is. I just want to write a series of post showing how I write a simple web application using the framework.


The application will have a frontend interacting with services in the backend. Tentatively, that's the big picture.

The story is simple.
1. User login into the system.
2. User browse the products from the inventory.
3. User adds some item into the shopping cart.
4. User proceed with the payment process.
5. Transaction recorded.

I'll research, learn, and share the knowledge as I work on the application. You people can benefit from it. 

Stay tuned for the upcoming post.