Filter by title
Document Options

There are multiple versions of this document. Pick the options that suit you best.

UI:
Database:

Microservice Tutorial Part 05: Building the Ordering service

In the previous part, we created the Ordering microservice. In this part, we will implement the functionality of the Ordering microservice manually. We will not use ABP Suite to generate the code. Instead, we will create the necessary code step by step to understand the details better.

Creating the Order Entity

We will start by creating the Order entity, which will represent an order in our system. We'll add this entity to the CloudCrm.OrderingService project. Create a new folder named Entities and create a file Order.cs inside it:

using CloudCrm.OrderingService.Enums;
using Volo.Abp.Domain.Entities.Auditing;

namespace CloudCrm.OrderingService.Entities;

public class Order : CreationAuditedAggregateRoot<Guid>
{
    public Guid ProductId { get; set; }
    public string CustomerName { get; set; }
    public OrderState State { get; set; }
}

To keep this example simple, we allow users to include only a single product within an order. The Order entity inherits from the CreationAuditedAggregateRoot<> class. This class, provided by the ABP Framework, includes common properties like Id, CreationTime, CreatorId, etc.

Adding the OrderState Enum

We also need to define the OrderState enum. In the CloudCrm.OrderingService.Contracts project, create a folder named Enums and create a file OrderState.cs inside it:

namespace CloudCrm.OrderingService.Enums;

public enum OrderState : byte
{
    Placed = 0,
    Delivered = 1,
    Canceled = 2
}

The final solution structure should look like this:

vs-ordering-entity

Configuring the Database Mapping

First, we need to add the Order entity to the OrderingServiceDbContext class. Open the OrderingServiceDbContext class in the CloudCrm.OrderingService project, located in the Data folder, and add the following code to the DbSet properties:

using CloudCrm.OrderingService.Entities;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DistributedEvents;
using Volo.Abp.EntityFrameworkCore.Modeling;

namespace CloudCrm.OrderingService.Data;

[ConnectionStringName(DatabaseName)]
public class OrderingServiceDbContext :
    AbpDbContext<OrderingServiceDbContext>,
    IHasEventInbox,
    IHasEventOutbox
{
    public const string DbTablePrefix = "";
    public const string DbSchema = null;
    
    public const string DatabaseName = "OrderingService";
    
    public DbSet<IncomingEventRecord> IncomingEvents { get; set; }
    public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }
    public DbSet<Order> Orders { get; set; } // NEW: ADD DBSET PROPERTY

    // Code omitted for brevity
}

Next, we need to configure the database mapping for the Order entity. We'll use the EF Core Fluent API for this configuration. In the OrderingServiceDbContext class add the following code to the OnModelCreating method:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    builder.ConfigureEventInbox();
    builder.ConfigureEventOutbox();

    builder.Entity<Order>(b =>
    {
        // Configure table name
        b.ToTable("Orders");

        // Always call this method to set base entity properties
        b.ConfigureByConvention();

        // Properties of the entity
        b.Property(q => q.CustomerName).IsRequired().HasMaxLength(120);
    });
}

In this code snippet, we configure the Order entity to use the Orders table in the database. We also specify that the CustomerName property is required and has a maximum length of 120 characters.

Add a Database Migration

Now, we can add a new database migration. You can use Entity Framework Core's Add-Migration (or dotnet ef migrations add) terminal command, but in this tutorial, we will use ABP Studio's shortcut UI.

Please stop the applications if they are running and ensure that the solution has built. You can right-click the CloudCrm.OrderingService (under the services folder) on ABP Studio Solution Explorer and select the Dotnet CLI -> Graph Build command.

Right-click the CloudCrm.OrderingService package and select the EF Core CLI -> Add Migration command:

abp-studio-add-entity-framework-core-migration

The Add Migration command opens a new dialog to get a migration name Added_Order_Entity:

abp-studio-entity-framework-core-add-migration-order

Once you click the OK button, a new database migration class is added to the Migrations folder of the CloudCrm.OrderingService project:

visual-studio-new-migration-class

The changes will be applied to the database during the next application startup. For more details, refer to the database migrations on service startup section.

Creating the Application Service

Now, we will create the application service to manage the Order entity.

Defining the Application Service Contract

First, we need to define the application service contract under the CloudCrm.OrderingService.Contracts project. Return to your IDE, open the CloudCrm.OrderingService module's .NET solution and create an IOrderAppService interface under the Services folder for the CloudCrm.OrderingService.Contracts project:

using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace CloudCrm.OrderingService.Services;

public interface IOrderAppService : IApplicationService
{
    Task<List<OrderDto>> GetListAsync();
    Task CreateAsync(OrderCreationDto input);
}

Defining the Data Transfer Objects

Next, we need to define the data transfer objects (DTOs) for the Order entity. The GetListAsync and CreateAsync methods will use these DTOs to communicate with the client applications.

Create a OrderCreationDto class under the Services folder in the CloudCrm.OrderingService.Contracts project:

using System;
using System.ComponentModel.DataAnnotations;

namespace CloudCrm.OrderingService.Services;

public class OrderCreationDto
{
    [Required]
    [StringLength(150)]
    public string CustomerName { get; set; }

    [Required]
    public Guid ProductId { get; set; }
}

Create a OrderDto class under the Services folder in the CloudCrm.OrderingService.Contracts project:

using System;
using CloudCrm.OrderingService.Enums;

namespace CloudCrm.OrderingService.Services;

public class OrderDto
{
    public Guid Id { get; set; }
    public string CustomerName { get; set; }
    public Guid ProductId { get; set; }
    public OrderState State { get; set; }
}

The final solution structure should look like this:

vs-ordering-contracts

Implementing the Application Service

Now, we will implement the IOrderAppService interface in the OrderAppService class. Create an OrderAppService class under the Services folder in the CloudCrm.OrderingService project:

using System;
using System.Collections.Generic;
using CloudCrm.OrderingService.Entities;
using CloudCrm.OrderingService.Enums;
using CloudCrm.OrderingService.Localization;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace CloudCrm.OrderingService.Services;

public class OrderAppService : ApplicationService, IOrderAppService
{
    private readonly IRepository<Order, Guid>  _orderRepository;

    public OrderAppService(IRepository<Order, Guid> orderRepository)
    {
        LocalizationResource = typeof(OrderingServiceResource);
        _orderRepository = orderRepository;
    }

    public async Task<List<OrderDto>> GetListAsync()
    {
        var orders = await _orderRepository.GetListAsync();
        return ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
    }

    public async Task CreateAsync(OrderCreationDto input)
    {
        var order = new Order
        {
            CustomerName = input.CustomerName,
            ProductId = input.ProductId,
            State = OrderState.Placed
        };

        await _orderRepository.InsertAsync(order);
    }
}

In this code snippet, we inject the IRepository<Order, Guid> into the OrderAppService class. We use this repository to interact with the Order entity. The GetListAsync method retrieves a list of orders from the database and maps them to the OrderDto class. The CreateAsync method creates a new order entity and inserts it into the database.

Afterward, we need to configure the AutoMapper object to map the Order entity to the OrderDto class. Open the OrderingServiceApplicationAutoMapperProfile class in the CloudCrm.OrderingService project, located in the ObjectMapping folder, and add the following code:

using AutoMapper;
using CloudCrm.OrderingService.Entities;
using CloudCrm.OrderingService.Services;

namespace CloudCrm.OrderingService.ObjectMapping;

public class OrderingServiceApplicationAutoMapperProfile : Profile
{
    public OrderingServiceApplicationAutoMapperProfile()
    {
        CreateMap<Order, OrderDto>();
    }
}

Testing the Application Service

Now, we can test the OrderAppService class using the Swagger UI. Open the Solution Runner and right-click to CloudCrm.OrderingService project and select the Start command. After the application starts, you can open the Swagger UI by clicking to the Browse command:

ordering-service-swagger-ui

Expand the api/ordering/order API and click the Try it out button. Then, create a few orders by filling in the request body and clicking the Execute button:

ordering-service-order-swagger-ui

If you check the database, you should see the entities created in the Orders table:

sql-server-orders-database-table-records

Since we're using the Auto API Controller we don't need to create a controller for the OrderAppService. The ABP Framework automatically creates an API controller for the OrderAppService class. You can find the configuration in the CloudCrmOrderingServiceModule class, in the ConfigureAutoControllers method of the CloudCrm.OrderingService project.

Creating the User Interface

Generating the Proxies

Run the following command line under the Angular project folder to generate the UI proxy for the OrderingService:

abp generate-proxy -t ng -m ordering -u http://localhost:44311 --target ordering-service

Make sure the url port (example 44311) must be same with your OrderService's port.

For more information, please refer to the Service Proxies documentation.

Add Order Route

  • Create order-base.routes.ts file under the projects/ordering-service/config/src/providers folder and add the following code:

order-base.routes.ts

import { ABP, eLayoutType } from '@abp/ng.core';

import { eOrderingServiceRouteNames } from '../enums/route-names';

export const ORDER_BASE_ROUTES: ABP.Route[] = [
  {
    path: '/ordering-service/orders',
    parentName: eOrderingServiceRouteNames.OrderingService,
    name: 'OrderingService::Menu:Orders',
    layout: eLayoutType.application,
    breadcrumbText: 'OrderingService::Orders',
  },
];
  • Create order-route.provider.ts file under the projects/ordering-service/config/src/providers folder and add the following code:

order-route.provider.ts

import { inject, provideAppInitializer } from '@angular/core';
import { ABP, RoutesService } from '@abp/ng.core';
import { ORDER_BASE_ROUTES } from './order-base.routes';

export const ORDERS_ORDER_ROUTE_PROVIDER = [
  provideAppInitializer(() => {
    configureRoutes();
  }),
];

function configureRoutes() {
  const routesService = inject(RoutesService);
  const routes: ABP.Route[] = [...ORDER_BASE_ROUTES];
  routesService.add(routes);
}
  • Open the projects/ordering-service/config/src/providers/route.provider.ts file and add ORDERS_ORDER_ROUTE_PROVIDER to the ORDER_SERVICE_PROVIDERS array as following code:

route.provider.ts

import { eLayoutType, RoutesService } from '@abp/ng.core';
import {
  EnvironmentProviders,
  inject,
  makeEnvironmentProviders,
  provideAppInitializer,
} from '@angular/core';
import { eOrderingServiceRouteNames } from '../enums/route-names';
import { ORDERS_ORDER_ROUTE_PROVIDER } from './order-route.provider';


export const ORDER_SERVICE_ROUTE_PROVIDERS = [
  provideAppInitializer(() => {
    configureRoutes();
  }),
];

export function configureRoutes() {
  const routesService = inject(RoutesService);
  routesService.add([
    {
      path: '/order-service',
      name: eOrderingServiceRouteNames.OrderService,
      iconClass: 'fas fa-book',
      layout: eLayoutType.application,
      order: 3,
    },
  ]);
}

const ORDER_SERVICE_PROVIDERS: EnvironmentProviders[] = [
  ...ORDER_SERVICE_ROUTE_PROVIDERS,
  ...ORDERS_ORDER_ROUTE_PROVIDER
];

export function provideOrderService() {
  return makeEnvironmentProviders(ORDER_SERVICE_PROVIDERS);
}
  • Do not forget adding provideOrderService() to the providers inside app.config.ts as follows:
import { provideOrderService } from '@order-service/config';

export const appConfig: ApplicationConfig = {
  providers: [ 
    // ...
    provideOrderService()
  ],
};
  • Lastly, you need to update the APP_ROUTES array in app.routes.ts file as follows:
// app.routes.ts
export const APP_ROUTES: Routes = [
  // ...
  {
    path: 'order-service',
    children: ORDER_SERVICE_ROUTES,
  },
];
// order-service.routes.ts
export const ORDER_SERVICE_ROUTES: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: RouterOutletComponent,
  },
  { path: 'orders', children: ORDER_ROUTES },
];

Create Order Page

  • Create order.component.ts file under the projects/ordering-service/src/lib/order folder as following code:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OrderDto, OrderService } from './proxy/ordering-service/services';

@Component({
  selector: 'lib-order',
  templateUrl: './order.component.html',
  styleUrl: './order.component.css'
  imports: [CommonModule]
})
export class OrderComponent {

  items: OrderDto[] = [];

  constructor(private readonly proxy: OrderService) {
    this.proxy.getList().subscribe((res) => {
      this.items = res;
    });
  }
  
}
  • Create order.component.html file under the projects/ordering-service/src/lib/order folder as following code:
<div class="card">
    <div class="card-body">
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>Order ID</th>
                    <th>Product Id</th>
                    <th>Customer Name</th>
                </tr>
                <tr *ngFor="let item of items">
                    <td>{{item.id}}</td>
                    <td>{{item.productId}}</td>
                    <td>{{item.customerName}}</td>
                </tr>
            </thead>
        </table>
    </div>
</div>

Building and Running the Application

Now, we can run the application to see the changes. Please stop the applications if they are running. Then open the Solution Runner panel, right-click the CloudCrm root item, and select the Start command:

abp-studio-run-build-start

After the applications are started, you can Browse and navigate to the Orders page to see the list of orders:

web-orders-page

Great! We have successfully implemented the Ordering module. However, there is a problem:

  • We see Product's GUID ID instead of its name. This is because the Ordering microservice has no integration with the Catalog microservice and doesn't have access to Product microservice's database to perform a JOIN query.

We will solve this problem in the next part by implementing an integration service between the Ordering and Catalog microservices.

Summary

In this part, we implemented the Ordering module manually. We created the Order entity, the OrderState enum, the OrderAppService application service, and the user interface for the Orders page. We also added a menu item to the sidebar to navigate to the Orders page.


Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.