laszczm
laszczm

Reputation: 173

API controller and underlying service - responsibility for arguments validation

Maybe it’s trivial situation but it’s confusing for me and I need some clarification how to deal with it in the right way.

To make it simple the model is as fallow:

User * - * Project

(User is assigned to many projects and project has many users assigned to).

I created rest controller for assigning and dismissing users from specific project:

…\Api\v1\Projects\{projectId}\users\{userId}

Two http action are allowed:

API Controller use service layer to perform these operation. Service interface is as fallow:

void projectService.assignUser(int projectId, int userId)
void projectService.dismissUser(int projectId, int userId)

Service use dbContext to perform these operation.

Question 1: Which element should be responsible for checking if projectId and userId are correct? IMHO it’s better to put this logic in service layer as it can be reuse.

Question 2: What these method should return in case the projectId and userid are incorrect (for instance: project/user does not exist or is not allowed to be assigned)?

My first thought was to return null value but I think it’s not very meaningful. Mainly because the similar methods in service returns null if enitiyId is incorrect. For instance: projectService.getProject(projectId) – return null if project doesn’t exists

The second though was to return bool. False is at least one of argument is incorrect

The third thought was to throw ArgumentException with message. It’s seems to be good but it makes api controller to catch the exception.

Upvotes: 1

Views: 249

Answers (2)

Nilu
Nilu

Reputation: 272

Answer 1: You should keep validating projectId/userId to service layer for reuse and Call those functions wherever needed.

Answer 2: Create a json based on valid/invalid projectId/userId . It might contain details related to user or project in case both are valid and return error message and error Id (Eg 1 for invalid project, 2 for Invalid User) in case of invalid Ids.

a) JsonObject projectService.assignUser(int projectId, int userId)

Eg:
Valid Ids (both):

{"stat": "ok", "userId": "userId Here", "projectId": "projectId Here"}

Invalid Project Id:

{"errorMsg":"Invalid Project Id","stat":"fail","error":1}

b) JsonObject projectService.dismissUser(int projectId, int userId)

Valid Ids (both):

{"stat": "ok"}

Invalid User Id:

{"errorMsg":"Invalid User Id","stat":"fail","error":2}

Upvotes: 0

FailedUnitTest
FailedUnitTest

Reputation: 1800

Q1) I think placing them in the Service Layer is a valid idea, I can think of many scenarios where you may want to reuse these methods.

Q2) This one is a bit tricky and subject to opinion, but it really depends on how you want to communicate with your business logic layer (which is where those methods would probably live). One approach that I used recently was creating a special Service Message Class to communicate between the Service Layer and the Business Layer (I presume you can use this to communicate between the presentation layer and the service layer as well with some changes). A class may look like this:

public class BusinessLogicMessage<T> where T : new()
    {
        public BusinessLogicMessage(T result)
        {
            Result = result;
            Status = BusinessLogicStatus.Success;
            Message = string.Empty;
        }

        public BusinessLogicMessage(T result, BusinessLogicStatus status, string message)
        {
            Result = result;
            Status = status;
            Message = message;
        }

        public BusinessLogicStatus Status { get; set; }
        public string Message { get; set; }
        public T Result { get; set; }
    }

    public class BusinessLogicMessage
    {
        public BusinessLogicMessage()
        {
            Status = BusinessLogicStatus.Success;
            Message = string.Empty;
        }

        public BusinessLogicMessage(BusinessLogicStatus status, string message)
        {
            Status = status;
            Message = message;
        }

        public BusinessLogicStatus Status { get; set; }
        public string Message { get; set; }
    }

    public enum BusinessLogicStatus
    {
        Success,
        Failure,
        Warning
    }

So, if your method succeeds, you simply return the default constructor of the class, which has a status of Success. If failure or else happens you can add details. If you need to return a special result object for that method you can attach it to Result

My two cents.

Updated for better answer.

Upvotes: 1

Related Questions