Services

From SocialStack
Revision as of 17:37, 5 July 2022 by 2a02:c7f:22be:a500:498:3225:1a16:a72d (talk) (Created page with "Services are the way how parts of a SocialStack API communicate with each other. They are essentially the code to code interfaces between different content types or bits of fu...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Services are the way how parts of a SocialStack API communicate with each other. They are essentially the code to code interfaces between different content types or bits of functionality inside your API. They are instanced once at startup and can can use dependency injection to access other services:


public ThingService(UserService userService) {

    Events.Thing.BeforeCreate.AddEventListener(async (Context context, Thing thing) => {
        if(thing == null){
            return null;
        }
        
        // Get the creator user (this is likely the same as context.User).
        // Throws if context user is not permitted to access this data.
        var aUser = await userService.Get(context, thing.UserId);
        
        // Do something with aUser here!
        
        return thing;
    });

}

Getting a service

There's two ways to get a service - either by C# dependency injection:

    public ThingService(UserService userService) {

Or when dependency injection isn't available due to either a cyclical dependency or simply being outside the scope of another service, you can use Services.Get. Note that this call will return null if the services have not been instanced yet; you can guarantee success if you wait until at least the Events.Service.AfterStart event triggers.

    var userService = Services.Get<UserService>(); // In Startup namespace

Common Features

Services contain a variety of common features for doing common tasks such as getting an object, creating one etc. Here's the main usage examples of each:

  • Get - Get an entity from the service by its ID which is usually a uint.
    uint entityId = 14;
    var entity = await aService.Get(context, entityId);

entityId could be e.g. a user ID, aService is the UserService and entity is therefore a User object.

  • Delete - Deletes an entity by its ID which is usually a uint.
    uint entityId = 14;
    await aService.Delete(context, entityId);
    
    // Or:
    await aService.Delete(context, entity);

entityId could be e.g. a user ID, aService is the UserService and entity is therefore a User object.

  • Where - Used to list multiple entities from the service with a filter string. Note that it is possible to entirely avoid all allocations with this part of the API - ListAll is just one of the available methods.
    string token = "";
    var listOfResults = await aService.Where("Token=?").Bind(tokenString).ListAll(context);
  • Create - Creates an entity in the target service.
    
    var thingEntity = await aService.Create(context, new Thing(){
        Token = "123123"
    });
  • Update - Updates an entity in the target service. Update uses a callback mechanism as this helps avoid allocations and allows internal field comparisons to happen to be able to easily identify which fields have changed.
    var thingEntity = await aService.Update(context, thingEntity, (Context context, Thing toUpdate, Thing original) => {
        toUpdate.Token = "something-else";
    });

Service Permissions

The permissions system is also modular in that it adds an event handler on the various core events (like create, delete and so on) for a particular content type. This means that when you access a service from some other part of the code, your current context must be permitted to access it. It also means that the event handler can be removed should you wish to do so, although naturally, removing the permission system is likely not a good idea on any project!

Ignoring the permissions

You can, however, ask a particular request to ignore the permission system all together. This effectively just makes the above event handler skip itself. This happens in situations where the incoming user is not permitted to generally access a certain piece of data, but the API internally needs to check something. Password resets are one example - the user (and their context) is anonymous, and anonymous users certainly shouldn't be able to publicly view any password resets. They can however potentially access one of them if a token value is correct, so that token lookup can be done by ignoring the permission system. That is done like so:

// Won't throw with a permissions failure
var aUser = await userService.Get(context, thing.UserId, DataOptions.IgnorePermissions);

DataOptions is generally available on many service methods - Create, Get, Where and so on - so you can skip over the permission check whenever necessary.