Scenario:
I have a situation where I need to refactor a Web service with a single API endpoint that uses complex logic to insert, update and fetch data from a database. The clients use one call, and depending on the data transfer object they send, the service is doing different things (the API was not designed by me so I cannot do anything about it - it needs to support existing clients).
So for example, a client sends the following JSON:
{
"Id": 1234,
"FacebookToken": "some facebook access token"
}
Then the service needs to respond with data fetched from the database. All the fields in JSON request are optional, and depending on what kind of combination of them have values or not, and on the current content of the database, the service needs to either create, update or not modify an entity in the database, and return the created/updated/selected entity to the client.
Question
My question is if you can think of any design patterns that I could use to avoid implementing the logic as bunch of nested if
statements in the domain layer. I'm not talking about splitting it into fine-grained functions (I already have that), but more about how to engage object orientation and polymorphism in the design, maybe utilize some well known design patterns.
I realize there is not one answer to this but I'm just wondering if anyone has faced similar design problem and have some good hints. Thanks in advance.
Example of a bad solution:
Here's a simple C# example of how I don't want the Service to be implemented. Note that it's intentionally simplistic to keep it short, normally I'd have interfaces and used dependency injection container etc. The part most relevant for the question here is the Service.PostRequest() method.
public class Request
{
public string Id { get; set; }
public string ExternalServiceToken { get; set; }
}
public class Response
{
public string Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"{Id}: {Name}";
}
}
public class UserEntity
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Service
{
private readonly Repository _repository;
private readonly ExternalService _externalService;
public Service(Repository repository, ExternalService externalService)
{
_repository = repository;
_externalService = externalService;
}
public Response PostRequest(Request request)
{
if (request == null) // create new entity
{
return ResponseFactoryMethod(_repository.Create(null));
}
UserEntity entity;
if (request.Id != null) // requested specific entity
{
entity = _repository.SelectById(request.Id);
if (request.ExternalServiceToken != null && entity.Name == null) // update entity with Name
{
entity.Name = _externalService.GetName(request.ExternalServiceToken);
_repository.AssignName(entity.Id, entity.Name);
}
return ResponseFactoryMethod(entity);
}
// entity.Id == null
if (request.ExternalServiceToken != null)
{
string name = _externalService.GetName(request.ExternalServiceToken);
entity = _repository.SelectByName(name); // try to find entity by Name, otherwise create
if (entity == null)
{
entity = _repository.Create(name);
}
return ResponseFactoryMethod(entity);
}
return ResponseFactoryMethod(_repository.Create(null));
}
private Response ResponseFactoryMethod(UserEntity entity)
{
var response = new Response();
response.Id = entity.Id;
response.Name = entity.Name;
return response;
}
}
public class ExternalService
{
private Dictionary<string, string> _names = new Dictionary<string, string>
{
{ "abc", "John" },
{ "def", "Jane" },
{ "ghi", "Bob" }
};
public string GetName(string token)
{
return _names[token];
}
}
public class Repository
{
private readonly List<UserEntity> _entities = new List<UserEntity>();
public UserEntity Create(string name)
{
var newUser = new UserEntity { Id = _entities.Count.ToString(), Name = name };
_entities.Add(newUser);
return newUser;
}
public UserEntity SelectById(string id)
{
return _entities.SingleOrDefault(e => e.Id == id);
}
public UserEntity SelectByName(string name)
{
return _entities.SingleOrDefault(e => e.Name == name);
}
public void AssignName(string id, string name)
{
UserEntity user = SelectById(id);
if (user != null)
{
user.Name = name;
}
}
}
public class Client
{
private static Service _service;
public static void Main(string[] args)
{
var repository = new Repository();
_service = new Service(repository, new ExternalService());
RequestFromService(null);
RequestFromService(new Request {Id = "0", ExternalServiceToken = "abc"});
RequestFromService(new Request());
RequestFromService(new Request { Id = "1", ExternalServiceToken = "def" });
RequestFromService(new Request {ExternalServiceToken = "ghi"});
RequestFromService(new Request {ExternalServiceToken = "ghi" });
Console.ReadLine();
}
static void RequestFromService(Request request)
{
Response response = _service.PostRequest(request);
string responseAsString = response != null ? response.ToString() : "null";
Console.WriteLine("Response from service: " + responseAsString);
}
}