Skip to content

Why Fox Biz/Data Service Web API?

이 문서는 Fox Biz/Data Service Web API 를 사용해야 하는 이유와 Fox Biz/Data Service Web API 를 사용했을 때의 장점에 대해 구체적으로 설명합니다. Fox Biz/Data Service Web API 를 잘 이해하기 위해서는 전통적인 다계층 앱 아키텍처와 이 아키텍처의 문제점을 먼저 이해하는 것이 도움이 됩니다. Fox Biz/Data Service Web API 는 이러한 문제점을 해결하기 위해 설계되었기 때문입니다.

전통적인 앱 아키텍처

다음 그림은 전형적인 다계층 앱 아키텍처를 보여줍니다. 이 아키텍처에서는 프레젠테이션(클라이언트) 계층, 서비스 계층, 비즈니스 로직 계층, 데이터 액세스 계층이 명확하게 분리되어 있습니다. 프레젠테이션 계층은 사용자 인터페이스와 사용자 경험을 담당하며, 서비스 계층은 원격 클라이언트와의 통신을 담당합니다. 이후,비즈니스 로직 계층은 애플리케이션의 핵심 기능과 규칙을 구현하고 데이터 액세스 계층은 데이터베이스와의 상호 작용을 담당합니다.

전통적인 앱 아키텍처

서비스 계층

서비스 계층은 브라우저 상에서 수행되는 웹 클라이언트나 데스크톱 앱과 같은 원격 클라이언트가 비즈니스 로직과 데이터 액세스 코드를 호출할 수 있도록 하는 계층입니다. 이 계층은 일반적으로 WCF, ASP.NET Web API, gRPC 와 같은 통신 프레임워크를 사용하여 구현됩니다.

다음 코드는 ASP.NET Web API 를 사용하여 서비스 계층을 구현한 전형적인 예제입니다.

[Route("api/orders")]
public class OrdersController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetOrderById(int id)
    {
        using OrdersBiz biz = new();
        Order? order = biz.GetOrderById(id);
        return Ok(order);
    }

    [HttpPost("new")]
    public IActionResult CreateOrder([FromBody] OrderDTO? dto_order)
    {
        Order order = dto_order.Order;
        OrderDetail[]? details = dto_order.Details;
        using OrdersBiz biz = new();
        int orderId = biz.InsertOrder(order, details);
        order.Order_Id = orderId;
        return CreatedAtAction(nameof(GetOrderById), new { id = orderId }, order);
    }
}

비즈니스 로직 계층

비즈니스 로직 계층은 애플리케이션의 핵심 기능과 규칙을 구현하며 이 과정에서 트랜잭션을 관리하는 계층입니다. 이 계층은 일반적으로 서비스 계층에서 호출되며, 데이터 액세스 계층을 사용하여 데이터베이스와 상호 작용합니다.

다음 코드는 비즈니스 로직 계층을 구현한 전형적인 예제입니다. 이 예제에서는 주문과 주문 상세 정보를 데이터베이스에 삽입하는 트랜잭션을 사용하고 있습니다.

public class OrdersBiz
{
    public Order? GetOrderById(int id)
    {
        using OrdersDac dac = new();
        return dac.GetOrderById(id);
    }

    public int InsertOrder(Order order, OrderDetail[]? details)
    {
        using TransactionScope scope = new(TransactionScopeOption.Required);
        using OrdersDac dac = new();
        int orderId = dac.InsertOrder(order);
        if (details != null)
        {
            foreach (var detail in details)
            {
                detail.Order_Id = orderId;
                dac.InsertDetails(detail);
            }
        }
        scope.Complete();
        return orderId;
    }
}

데이터 액세스 계층

데이터 액세스 계층은 데이터베이스와의 상호 작용을 담당하는 계층입니다. 이 계층은 일반적으로 비즈니스 로직 계층에서 호출되며, SQL 쿼리를 실행하여 데이터를 삽입, 업데이트, 삭제 또는 조회하는 기능을 제공합니다.

다음 코드는 NeoDEEX 의 SQL 매퍼 기능인 Fox Query를 사용하여 데이터 액세스 계층을 구현한 전형적인 예제입니다.

public class OrdersDac : IOrdersDac
{
    public Order? GetOrderById(int id)
    {
        using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
        List<Order> orders = dbAccess.ExecuteQueryList<Order>("orders.get_order_by_id", new { order_id = id });
        if (orders.Count == 0) return null;
        return orders[0];
    }

    public int InsertOrder(Order order)
    {
        using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
        object? result = dbAccess.ExecuteQueryScalar("orders.insert_order", order);
        return Convert.ToInt32(result);
    }

    public void InsertDetails(OrderDetail detail)
    {
        using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
        dbAccess.ExecuteQueryNonQuery("orders.insert_detail", detail);
    }
}

문제점

전통적인 다계층 앱 아키텍처는 명확한 계층 분리를 통해 유지 보수성과 확장성을 높이는 장점이 있지만, 다음과 같은 문제점도 존재합니다.

불필요한 서비스 계층 코드

서비스 계층의 코드들은 절대 다수가 클라이언트의 호출 매개변수를 비즈니스 로직에 전달하는 단순한 전달자 역할을 합니다. 이러한 코드들은 비즈니스 로직과 데이터 액세스 코드에 비해 상대적으로 단순하지만, 전체 애플리케이션에서 상당한 양이 존재할 수 있습니다. 이로 인해 개발자들은 서비스 계층의 코드를 작성하고 유지 보수하는 데 많은 시간을 소비하게 됩니다.

위 예제 코드에서도 서비스 계층은 단순히 OrdersBiz 클래스의 메서드를 호출하여 클라이언트의 요청을 처리하고 있습니다. 이러한 패턴은 애플리케이션의 규모가 커질수록 서비스 계층의 코드가 증가하게 되어 개발과 유지 보수에 부담이 될 수 있습니다.

불필요한 비즈니스 로직 계층 코드

비즈니스 로직 계층의 코드들도 절대 다수가 단순히 데이터 액세스 계층을 호출하여 데이터를 조회하거나 조작하는 역할을 합니다. 특히 단순 조회의 경우에는 비즈니스 로직이라고 할 만한 코드가 존재하지 않습니다. 비록 단순한 코드라 할지라도 전체 애플리케이션에서 상당한 양이 존재할 수 있으며, 이로 인해 개발자들은 비즈니스 로직 계층의 코드를 작성하고 유지 보수하는 데 많은 시간을 소비하게 됩니다.

위 예제 코드에서 OrdersBiz 클래스의 GetOrderById 메서드는 단순히 OrdersDac 클래스의 GetOrderById 메서드를 호출하여 데이터를 조회하고 있습니다. 이러한 단순 조회는 비즈니스 로직 계층을 건너뛰어 곧바로 데이터 액세스 계층을 호출하는 것이 더 효율적일 수 있습니다.

불필요한 데이터 액세스 계층 코드

데이터 액세스 계층의 코드들도 절대 다수가 단순히 SQL 쿼리를 실행하여 데이터를 조회하거나 조작하는 역할을 합니다. 하지만 Fox Query 와 같은 SQL 매퍼를 사용하면 SQL 쿼리를 XML 파일로 분리하여 관리할 수 있기 때문에 다수의 데이터 액세스 계층의 코드들은 대단히 단순하게 SQL 매퍼를 호출하는 역할을 하게 됩니다. 특히 단순 조회 역할을 수행하는 데이터 액세스 계층의 코드들이 그러합니다.

위 예제 코드에서 OrdersDac 클래스의 GetOrderById 메서드는 단순히 Fox Query 를 사용하여 SQL 쿼리를 실행하고 데이터를 조회하는 역할을 하고 있습니다. 이러한 단순 조회는 데이터 액세스 계층의 코드가 불필요하게 많아지는 원인이 될 수 있습니다.

문제 해결책

위에서 설명한 기존 어플리케이션 아키텍처의 문제점들을 해결하기 위해

  • Fox Data Service 는 코드를 작성하지 않고 .foxml 파일만을 사용하여 데이터베이스에 대한 액세스를 제공하며,

  • Fox Biz Service 는 서비스 계층 작성하지 않더라도 클라이언트가 비지니스 로직을 호출할 수 있도록 해주며,

  • Fox Web API 는 별도의 코드를 작성하지 않거나 최소한의 코드만으로 Fox Biz/Data Service 를 클라이언트가 호출할 수 있도록 해줍니다.

다음 그림에서 파란색 부분만이 개발자가 작성하는 코드들 나타내며 나머지 부분은 NeoDEEX 의 Fox Biz/Data Service Web API 가 자동으로 제공하는 기능들입니다.

Fox Biz/Data Service Web API 아키텍처

Fox Data Service

Fox Data Service 는 .foxml 파일(들)을 사용하여 데이터베이스에 대한 액세스를 제공하는 서비스입니다. Fox Data Service 는 SQL 매퍼인 Fox Query 와 긴밀하게 통합되어 있으며, .foxml 파일을 사용하여 SQL 쿼리를 정의하고 관리할 수 있습니다. Fox Data Service 는 데이터 액세스 계층의 코드를 작성하지 않고도 데이터베이스에 대한 액세스를 제공할 수 있도록 해줍니다.

다음 코드 조각은 ASP.NET Core 의 Minimal Web API 기능을 사용하여 Fox Data Service 를 호출하는 예제입니다. Fox Data Service 를 호출하기 위해 FoxDataRequest 객체를 생성하고 필요한 쿼리 ID 와 매개변수를 설정하고 Execute 시리즈 메서드를 호출하면 됩니다.

    ...... 
    app.MapGet("/orders/{id}", (int id) =>
    {
        var request = new FoxDataRequest("orders.get_order_by_id");
        request.Parameters.Add("order_id", id);
        using var service = new FoxDataService();
        var response = service.ExecuteDataSet(request);
        var order = GetOrderFromDataRow(response.DataSet.Tables[0].Rows[0]);
        return order;
    });
    ......

Note

개발자가 Fox Data Service 를 위와 같이 직접 호출하는 경우는 드물며, 일반적으로는 Fox Web API 를 통해 원격 클라이언트가 Fox Data Service 를 호출하는 형태로 사용됩니다. 다음 섹션에서 Fox Web API 를 통해 Fox Data Service 를 호출하는 예제를 보여줍니다.

위 호출 예는 orders.foxml 파일에서 get_order_by_id 라는 ID로 정의된 SQL 쿼리를 호출하여 주문 정보를 조회합니다. Foxml 파일에 대한 상세한 내용은 Foxml 파일 가이드 문서를 참고 하십시요. 다음은 orders.foxml 파일에서 get_order_by_id 라는 ID로 정의된 SQL 쿼리의 예입니다.

<?xml version="1.0" encoding="utf-8" ?>
<queryMap xmlns="http://schema.neodeex.net/fx/foxml/2023/04/">
  <statements>
    <statement id="get_order_by_id">
      <text>
        SELECT * FROM t_orders WHERE order_id = #order_id#
      </text>
    </statement>
    ...... 다른 쿼리들 생략 ......
  </statements>
</queryMap>

이 처럼 Fox Data Service 를 통해 비즈니스 로직 계층이나 데이터 액세스 계층 코드를 작성하지 않고도 .foxml 파일을 통해 데이터베이스에 대한 액세스를 제공할 수 있습니다.

Fox Biz Service

Fox Biz Service 는 클라이언트가 비지니스 로직을 클래스 아이디와 메서드 아이디로 구분하여 동적으로 호출할 수 있도록 해주는 서비스입니다. Fox Biz Service 를 활용하면 서비스 계층을 작성하지 않더라도 클라이언트가 비지니스 로직을 호출할 수 있으며, 비즈니스 로직 계층을 작성하지 않더라도 클라이언트가 데이터 액세스 계층을 호출할 수 있습니다.

다음 에제 코드는 FoxBizClassAttributeFoxBizMethodAttribute 를 사용하여 OrdersBiz 클래스의 InsertOrder 메서드를 Fox Biz Service 에 등록합니다. 입니다. 특히 OrderBiz 클래스는 Fox Transactions 기능에서 제공하는 FoxBizBase 클래스에서 파생하여 자동으로 트랜잭션이 시작하며 예외 발생 여부에 따라 트랜잭션이 커밋 또는 롤백되는 기능을 제공하기 때문에 InsertOrder 메서드에서는 별도의 트랜잭션 코드를 작성할 필요가 없습니다.

[FoxBizClass("OrdersBiz")]
public class OrdersBiz : FoxBizBase, IOrdersBiz
{
    [FoxBizMethod]
    public int InsertOrder(Order order, OrderDetail[]? details)
    {
        using OrdersDac dac = new();
        IOrdersDac itf = dac.CreateExecution<IOrdersDac>();
        int orderId = itf.InsertOrder(order);
        if (details != null)
        {
            foreach (var detail in details)
            {
                itf.InsertDetails(detail);
            }
        }
        return orderId;
    }
}

비즈니스 로직이 필요한 호출자는 클래스 아이디로 OrderBiz를 사용하고 메서드 아이디로 InsertOrder 를 사용하여 Fox Biz Service 를 호출하면 이 비즈니스 로직을 호출할 수 있습니다. 다음은 ASP.NET Core 의 Minimal Web API 에서 Fox Biz Service 를 호출하는 예제입니다. Fox Biz Service 를 호출하기 위해 FoxBizRequest 객체를 생성하고 필요한 클래스 ID, 메서드 ID 와 매개변수를 설정하고 Execute 시리즈 메서드를 호출하면 됩니다.

    ......
    app.MapPost("/orders/new", (OrderInfo orderDTO) =>
    {
        var request = new FoxBizRequest("OrdersBiz", "InsertOrder");
        request.Parameters.Add("order", orderDTO.order);
        request.Parameters.Add("details", orderDTO.details);
        using var service = new FoxBizService();
        var response = service.Execute(request);
        return response;
    });
    .......

Note

개발자가 Fox Biz Service 를 위와 같이 직접 호출하는 경우는 드물며, 일반적으로는 Fox Web API 를 통해 원격 클라이언트가 Fox Biz Service 를 호출하는 형태로 사용됩니다. 다음 섹션에서 Fox Web API 를 통해 Fox Biz Service 를 호출하는 예제를 보여줍니다.

Note

Fox Biz Service 를 통해 Fox Transactions 기반의 트랜잭션 메서드를 호출하는 경우, CreateExecution<T> 메서드를 통해 수행 프록시(Execution Proxy) 생성할 필요가 없습니다. Fox Biz Service 의 디스패처는 호출 대상이 FoxComponentBase 에서 파생된 클래스의 트랜잭션 메서드인 경우 자동으로 전처리/후처리가 수행되도록 제어하기 때문입니다.

Fox Web API

Fox Web API 는 HTTP/JSON 기반의 원격 호출 통신 서비스로서 NeoDEEX 기반 앱이 서비스 계층을 작성하지 않고도 Fox Biz/Data Service 를 호출할 수 있도록 해 줍니다. Fox Web API 를 사용하기 위해서는 Fox Web API 기능을 위한 MVC 컨트롤러를 설정하는 것이 가장 간편합니다.

다음 코드는 ASP.NET Core 의 Main 메서드에서 Fox Web API 기능을 설정하는 예제입니다. AddFoxWebApiControllers 확장 메서드를 사용하여 Fox Web API 가 제공하는 MVC 컨트롤러들을 추가하고, ConfigureBizService 확장 메서드를 사용하여 Fox Biz Service 를 위한 비즈 모듈(비즈로직 클래스와 메서드가 포함된 어셈블리)을 등록하면 됩니다.

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddControllers()
        .AddFoxWebApiControllers();     // register MVC controllers for Fox Web API

    var app = builder.Build();
    app.ConfigureBizService();          // setup and register biz modules
    app.UseHttpsRedirection();
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
}

Fox Web API 는 HTTP 요청에 포함된 JSON 으로부터 FoxDataRequest 또는 FoxBizRequest 객체를 역직렬화(deserialize)하여 Fox Biz/Data Service 로 전달하며 Fox Biz/Data Service 로부터 반환된 결과를 다시 JSON 으로 직렬화하여 HTTP 응답으로 반환하는 역할을 수행합니다. 따라서 앞서 Fox Biz/Data Service 호출 예제로 살펴보았던 MapGet 이나 MapPost 호출 코드를 작성할 필요가 없습니다.

Note

Fox Web API 가 수신하는 HTTP 요청은 POST 만 유효하며 BODY 에서 JSON 형태로 FoxDataRequest 또는 FoxBizRequest 객체를 전달해야 한다는 제약 사항이 존재합니다. GET 요청이나 URL 쿼리 문자열을 통해 매개변수를 전달하는 방식은 기본적으로 지원하지 않습니다. 하지만 필요하다면 Fox Web API 의 Miminal Web API 지원 기능을 활용하여 GET 요청이나 URL 쿼리 문자열을 통해 매개변수를 전달하는 방식으로 Fox Biz/Data Service 를 호출할 수 있도록 커스터마이징할 수 있습니다.

다음은 Fox Web API 를 통해 Fox Data Service 를 호출하는 HTTP 요청의 예를 보여줍니다. HTTP 요청의 BODY 에 포함된 JSON 은 FoxDataRequest 객체로 역직렬화되어 Fox Data Service 로 전달됩니다. queryId.foxml 파일에서 정의된 SQL 쿼리의 아이디를 나타내며, parameters 는 SQL 쿼리에 전달되는 매개변수들을 나타냅니다.

1
2
3
4
5
6
7
8
9
POST http://localhost:5069/api/dataservice/executedataset
Content-Type: application/json

{
  "queryId": "orders.get_order_by_id",
  "parameters": {
    "order_id": 1
  }
}

다음은 Fox Web API 를 통해 Fox Biz Service 를 호출하는 HTTP 요청의 예를 보여줍니다. HTTP 요청의 BODY 에 포함된 JSON 은 FoxBizRequest 객체로 역직렬화되어 Fox Biz Service 로 전달됩니다. classId 는 비즈니스 로직 클래스의 아이디를 나타내며, methodId 는 비즈니스 로직 메서드의 아이디를 나타냅니다. parameters 는 비즈니스 로직 메서드에 전달되는 매개변수들을 나타냅니다.

POST http://localhost:5069/api/bizservice/execute
Accept: application/json
Content-Type: application/json

{
  "classId": "OrdersBiz",
  "methodId": "InsertOrder",
  "parameters": {
    ......
  }
}

Summary

Fox Biz/Data Service Web API 는 전통적인 다계층 앱 아키텍처에서 발생하는 불필요한 서비스 계층, 비즈니스 로직 계층, 데이터 액세스 계층 코드의 문제점을 해결하기 위해 설계된 서비스입니다. Fox Data Service 는 .foxml 파일을 사용하여 데이터베이스에 대한 액세스를 제공하며, Fox Biz Service 는 클래스 아이디와 메서드 아이디로 구분하여 비즈니스 로직을 동적으로 호출할 수 있도록 해줍니다. Fox Web API 는 HTTP/JSON 기반의 원격 호출 통신 서비스로서 NeoDEEX 기반 앱이 서비스 계층을 작성하지 않고도 Fox Biz/Data Service 를 호출할 수 있도록 해 줍니다. Fox Biz/Data Service Web API 를 사용하면 개발자는 불필요한 코드 작성과 유지 보수에서 벗어나 애플리케이션의 핵심 기능 구현에 집중할 수 있습니다.