Progettazione dell'architettura per Web API con endpoint singolo

4

Scenario:

Ho una situazione in cui devo refactoring un servizio Web con un singolo endpoint API che utilizza logica complessa per inserire, aggiornare e recuperare i dati da un database. I client usano una chiamata e, a seconda dell'oggetto di trasferimento dati che inviano, il servizio sta facendo cose diverse (l'API non è stata progettata da me quindi non posso farci nulla: deve supportare i client esistenti).

Quindi, ad esempio, un client invia il seguente JSON:

{
    "Id": 1234,
    "FacebookToken": "some facebook access token"
}

Quindi il servizio deve rispondere con i dati recuperati dal database. Tutti i campi nella richiesta JSON sono opzionali e, a seconda del tipo di combinazione di essi hanno valori o meno, e sul contenuto corrente del database, il servizio deve creare, aggiornare o non modificare un'entità nel database, e restituire l'entità creata / aggiornata / selezionata al client.

Domanda

La mia domanda è se riesci a pensare a schemi di progettazione che potrei usare per evitare di implementare la logica come una serie di istruzioni% co_de nidificate nel livello dominio. Non sto parlando di dividerlo in funzioni a grana fine (ho già questo), ma di più su come coinvolgere l'orientamento degli oggetti e il polimorfismo nel design, magari utilizzare alcuni modelli di design ben noti.

Mi rendo conto che non c'è una risposta a questo, ma mi chiedo solo se qualcuno ha affrontato problemi di progettazione simili e ha qualche buon suggerimento. Grazie in anticipo.

Esempio di soluzione non valida:

Ecco un semplice esempio in C # di come non desideri implementare il servizio. Si noti che è intenzionalmente semplicistico mantenerlo breve, normalmente avrei interfacce e usato il contenitore di iniezione delle dipendenze, ecc. La parte più rilevante per la domanda qui è il metodo Service.PostRequest ().

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);
    }
}
    
posta Caleb9 12.05.2016 - 09:31
fonte

1 risposta

1

Essendo stato il colpevole di tale API, sento il tuo dolore. Sono stato costretto dal mio capo a tornare indietro e rifarlo senza incidere sulle implementazioni esistenti (yuck) e trovare una risposta diversa.

Gran parte del refactoring che ho fatto è stato quello di usare le proprietà degli oggetti in entrata come chiavi di un dizionario e aggirarlo in quel modo. Quindi, ho aggiunto singoli endpoint che sono andati a queste funzioni indicate direttamente per gestire l'espansione e correggere i miei errori. Ad esempio, se il mio endpoint era / api / sync, il mio dizionario era simile a questo

<String, Function>
<"Facebook", ProcessFacebookRequest()>
<"PowerSchool", ProcessPowerSchoolRequest()>
//And so on...

Ho quindi aggiunto endpoint per ogni particolare richiesta "generica"

POST /api/sync/facebook
POST /api/sync/powerschool

Che è anche solo un pochino meglio. Raccomando caldamente uno schema di versioning per assicurarmi di avere la flessibilità di aggiornare e far crescere l'API come fa il tuo progetto. Quindi, in questo momento l'endpoint as-is va bene, ma vorrei strongmente prendere in considerazione qualcosa come /v2/sync/facebook/ che diventi uno standard nel prossimo futuro in modo da poter preservare le vecchie funzionalità e introdurre nuove funzionalità senza bugger.

    
risposta data 30.09.2016 - 15:17
fonte