-
-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Explores support for concurrency tokens using PostgreSQL
*Update (2021-11-08): Rebased on latest changes in master branch* Because we fetch the row before update and apply changes on that, a concurrency violation is only reported when two concurrent requests update the same row in parallel. Instead, we want to produce an error if the token sent by the user does not match the stored token. To do that, we need to convince EF Core to use that as original version. That's not too hard. Now the problem is that there is no way to send the token for relationships or deleting a resource. Skipped tests have been added to demonstrate this. We could fetch such related rows upfront to work around that, but that kinda defeats the purpose of using concurrency tokens in the first place. It may be more correct to fail when a user is trying to add a related resource that has changed since it was fetched. This reasoning may be a bit too puristic and impractical, but at least that's how EF Core seems to handle it. Solutions considerations: - Add 'version' to resource identifier object, so the client can send it. The spec does not explicitly forbid adding custom fields, however 'meta' would probably be the recommended approach. Instead of extending the definition, we could encode it in the StringId. - Once we have access to that token value, we need to somehow map that to 'the' resource property. What if there are multiple concurrency token properties on a resource? And depending on the database used, this could be typed as numeric, guid, timestamp, binary or something else. - Given that PostgreSQL uses a number (uint xmin), should we obfuscate or even encrypt that? If the latter, we need to add an option for api developers to set the encryption key. See also: json-api/json-api#600 json-api/json-api#824
- Loading branch information
Bart Koelman
committed
Nov 23, 2021
1 parent
15f5923
commit eacfc83
Showing
10 changed files
with
857 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System; | ||
using System.Net; | ||
using JetBrains.Annotations; | ||
using JsonApiDotNetCore.Serialization.Objects; | ||
|
||
namespace JsonApiDotNetCore.Errors | ||
{ | ||
/// <summary> | ||
/// The error that is thrown when data has been modified on the server since the resource was retrieved. | ||
/// </summary> | ||
[PublicAPI] | ||
public sealed class DataConcurrencyException : JsonApiException | ||
{ | ||
public DataConcurrencyException(Exception exception) | ||
: base(new ErrorObject(HttpStatusCode.Conflict) | ||
{ | ||
Title = "The concurrency token is missing or does not match the server version. " + | ||
"This indicates that data has been modified since the resource was retrieved." | ||
}, exception) | ||
{ | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
test/JsonApiDotNetCoreTests/IntegrationTests/ConcurrencyTokens/ConcurrencyDbContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using JetBrains.Annotations; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
// @formatter:wrap_chained_method_calls chop_always | ||
|
||
namespace JsonApiDotNetCoreTests.IntegrationTests.ConcurrencyTokens | ||
{ | ||
[UsedImplicitly(ImplicitUseTargetFlags.Members)] | ||
public sealed class ConcurrencyDbContext : DbContext | ||
{ | ||
public DbSet<Disk> Disks => Set<Disk>(); | ||
public DbSet<Partition> Partitions => Set<Partition>(); | ||
|
||
public ConcurrencyDbContext(DbContextOptions<ConcurrencyDbContext> options) | ||
: base(options) | ||
{ | ||
} | ||
|
||
protected override void OnModelCreating(ModelBuilder builder) | ||
{ | ||
// https://www.npgsql.org/efcore/modeling/concurrency.html | ||
|
||
builder.Entity<Disk>() | ||
.UseXminAsConcurrencyToken(); | ||
|
||
builder.Entity<Partition>() | ||
.UseXminAsConcurrencyToken(); | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
test/JsonApiDotNetCoreTests/IntegrationTests/ConcurrencyTokens/ConcurrencyFakers.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System; | ||
using Bogus; | ||
using JsonApiDotNetCore; | ||
using TestBuildingBlocks; | ||
|
||
// @formatter:wrap_chained_method_calls chop_always | ||
// @formatter:keep_existing_linebreaks true | ||
|
||
namespace JsonApiDotNetCoreTests.IntegrationTests.ConcurrencyTokens | ||
{ | ||
internal sealed class ConcurrencyFakers : FakerContainer | ||
{ | ||
private const ulong OneGigabyte = 1024 * 1024 * 1024; | ||
private static readonly string[] KnownFileSystems = ArrayFactory.Create("NTFS", "FAT32", "ext4", "XFS", "ZFS", "btrfs"); | ||
|
||
private readonly Lazy<Faker<Disk>> _lazyDiskFaker = new(() => | ||
new Faker<Disk>().UseSeed(GetFakerSeed()) | ||
.RuleFor(disk => disk.Manufacturer, faker => faker.Company.CompanyName()) | ||
.RuleFor(disk => disk.SerialCode, faker => faker.System.ApplePushToken())); | ||
|
||
private readonly Lazy<Faker<Partition>> _lazyPartitionFaker = new(() => | ||
new Faker<Partition>().UseSeed(GetFakerSeed()) | ||
.RuleFor(partition => partition.MountPoint, faker => faker.System.DirectoryPath()) | ||
.RuleFor(partition => partition.FileSystem, faker => faker.PickRandom(KnownFileSystems)) | ||
.RuleFor(partition => partition.CapacityInBytes, faker => faker.Random.ULong(OneGigabyte * 50, OneGigabyte * 100)) | ||
.RuleFor(partition => partition.FreeSpaceInBytes, faker => faker.Random.ULong(OneGigabyte * 10, OneGigabyte * 40))); | ||
|
||
public Faker<Disk> Disk => _lazyDiskFaker.Value; | ||
public Faker<Partition> Partition => _lazyPartitionFaker.Value; | ||
} | ||
} |
Oops, something went wrong.