diff --git a/Bahla.Backend.sln b/Bahla.Backend.sln
index 9061b29..784d2b0 100644
--- a/Bahla.Backend.sln
+++ b/Bahla.Backend.sln
@@ -3,7 +3,31 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35303.130
MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Tests", "src\Bahla.Tests\Bahla.Tests.csproj", "{F1392CEF-BD63-4208-8ADA-95DF407E0D28}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Domain", "src\Bahla.Domain\Bahla.Domain.csproj", "{543F323C-31BF-4BEA-9503-F9A26E963FC8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bahla.Persistence", "src\Bahla.Persistence\Bahla.Persistence.csproj", "{FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}"
+EndProject
Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F1392CEF-BD63-4208-8ADA-95DF407E0D28}.Release|Any CPU.Build.0 = Release|Any CPU
+ {543F323C-31BF-4BEA-9503-F9A26E963FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {543F323C-31BF-4BEA-9503-F9A26E963FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {543F323C-31BF-4BEA-9503-F9A26E963FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {543F323C-31BF-4BEA-9503-F9A26E963FC8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FBEE999C-7DFE-451B-AB01-7E7AE5C3ED95}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
diff --git a/src/Bahla.Domain/Bahla.Domain.csproj b/src/Bahla.Domain/Bahla.Domain.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/src/Bahla.Domain/Bahla.Domain.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/src/Bahla.Domain/Builders/Base/IEntityBuilder.cs b/src/Bahla.Domain/Builders/Base/IEntityBuilder.cs
new file mode 100644
index 0000000..d0ee200
--- /dev/null
+++ b/src/Bahla.Domain/Builders/Base/IEntityBuilder.cs
@@ -0,0 +1,13 @@
+using Bahla.Domain.Entities.Base;
+
+namespace Bahla.Domain.Builders.Base
+{
+ public interface IEntityBuilder where T : IEntity
+ {
+ ///
+ /// Build the entity.
+ ///
+ /// Entity
+ T Build();
+ }
+}
diff --git a/src/Bahla.Domain/Builders/Base/IRoleBuilder.cs b/src/Bahla.Domain/Builders/Base/IRoleBuilder.cs
new file mode 100644
index 0000000..13a40bd
--- /dev/null
+++ b/src/Bahla.Domain/Builders/Base/IRoleBuilder.cs
@@ -0,0 +1,37 @@
+using Bahla.Domain.Entities;
+using Bahla.Domain.Types;
+
+namespace Bahla.Domain.Builders.Base
+{
+ public interface IRoleBuilder : IEntityBuilder
+ {
+ ///
+ /// Defines the identifier for the role, if not provided, a new identifier will be generated.
+ ///
+ /// Identifier
+ /// Builder
+ IRoleBuilder WithIdentifier(Identifier identifier);
+
+ ///
+ /// Defines the name for the role.
+ ///
+ /// Role name
+ /// Builder
+ IRoleBuilder WithName(string roleName);
+
+
+ ///
+ /// Defines the value mask for the role. Maximum allowed is 32 bits (1 << 32).
+ ///
+ /// Role bitmask
+ /// Builder
+ IRoleBuilder WithValueMask(uint valueMask);
+
+ ///
+ /// Clones the role.
+ ///
+ /// Role
+ /// Builder
+ IRoleBuilder Clone(Role role);
+ }
+}
diff --git a/src/Bahla.Domain/Builders/RoleBuilder.cs b/src/Bahla.Domain/Builders/RoleBuilder.cs
new file mode 100644
index 0000000..0385d3f
--- /dev/null
+++ b/src/Bahla.Domain/Builders/RoleBuilder.cs
@@ -0,0 +1,61 @@
+using Bahla.Domain.Builders.Base;
+using Bahla.Domain.Entities;
+using Bahla.Domain.Exceptions;
+using Bahla.Domain.Specifications.Base;
+using Bahla.Domain.Types;
+
+namespace Bahla.Domain.Builders
+{
+ internal sealed class RoleNameIsNotEmptySpecification : CompositeSpecification
+ {
+ public override bool IsSatisfiedBy(RoleBuilder entity)
+ => !string.IsNullOrEmpty(entity._roleName) && !string.IsNullOrWhiteSpace(entity._roleName);
+ }
+
+
+ public sealed class RoleBuilder : IRoleBuilder
+ {
+ internal Identifier _identifier = Identifier.Generate;
+
+ internal string _roleName = string.Empty;
+
+ internal uint _valueMask;
+
+ public Role Build()
+ {
+ if (new RoleNameIsNotEmptySpecification()
+ .IsSatisfiedBy(this)) {
+ return new Role(this);
+ }
+
+ throw new EntityBuilderException("Role name cannot be null or empty");
+ }
+
+ public IRoleBuilder WithIdentifier(Identifier identifier)
+ {
+ _identifier = identifier;
+ return this;
+ }
+
+ public IRoleBuilder WithName(string roleName)
+ {
+ _roleName = roleName;
+ return this;
+ }
+
+ public IRoleBuilder WithValueMask(uint valueMask)
+ {
+ _valueMask = valueMask;
+ return this;
+ }
+
+ public IRoleBuilder Clone(Role role)
+ {
+ _identifier = role.UUID;
+ _roleName = role.RoleName;
+ _valueMask = role.ValueMask;
+
+ return this;
+ }
+ }
+}
diff --git a/src/Bahla.Domain/Entities/Base/Entity.cs b/src/Bahla.Domain/Entities/Base/Entity.cs
new file mode 100644
index 0000000..334d493
--- /dev/null
+++ b/src/Bahla.Domain/Entities/Base/Entity.cs
@@ -0,0 +1,6 @@
+using Bahla.Domain.Types;
+
+namespace Bahla.Domain.Entities.Base
+{
+ public abstract record Entity(Identifier UUID) : IEntity;
+}
diff --git a/src/Bahla.Domain/Entities/Base/IEntity.cs b/src/Bahla.Domain/Entities/Base/IEntity.cs
new file mode 100644
index 0000000..742b226
--- /dev/null
+++ b/src/Bahla.Domain/Entities/Base/IEntity.cs
@@ -0,0 +1,15 @@
+using Bahla.Domain.Types;
+
+namespace Bahla.Domain.Entities.Base
+{
+ ///
+ /// Interface that describes an independent entity.
+ ///
+ public interface IEntity
+ {
+ ///
+ /// Unique identifier for the entity.
+ ///
+ Identifier UUID { get; init; }
+ }
+}
diff --git a/src/Bahla.Domain/Entities/Role.cs b/src/Bahla.Domain/Entities/Role.cs
new file mode 100644
index 0000000..62c60af
--- /dev/null
+++ b/src/Bahla.Domain/Entities/Role.cs
@@ -0,0 +1,26 @@
+using Bahla.Domain.Builders;
+using Bahla.Domain.Builders.Base;
+using Bahla.Domain.Entities.Base;
+using Bahla.Domain.Types;
+
+namespace Bahla.Domain.Entities
+{
+ public sealed record Role : Entity
+ {
+ public string RoleName { get; init; }
+ public uint ValueMask { get; init; }
+ internal Role(Identifier identifier, string roleName, uint valueMask) : base(identifier)
+ {
+ RoleName = roleName;
+ ValueMask = valueMask;
+ }
+
+ internal Role(RoleBuilder builder) : base(builder._identifier)
+ {
+ RoleName = builder._roleName;
+ ValueMask = builder._valueMask;
+ }
+
+ public static IRoleBuilder Builder => new RoleBuilder();
+ }
+ }
diff --git a/src/Bahla.Domain/Exceptions/DomainException.cs b/src/Bahla.Domain/Exceptions/DomainException.cs
new file mode 100644
index 0000000..b65e74d
--- /dev/null
+++ b/src/Bahla.Domain/Exceptions/DomainException.cs
@@ -0,0 +1,4 @@
+namespace Bahla.Domain.Exceptions
+{
+ public abstract class DomainException(string message) : Exception(string.Concat("Domain Exception: ", " ", message)) { }
+}
diff --git a/src/Bahla.Domain/Exceptions/EntityBuilderException.cs b/src/Bahla.Domain/Exceptions/EntityBuilderException.cs
new file mode 100644
index 0000000..6d26df4
--- /dev/null
+++ b/src/Bahla.Domain/Exceptions/EntityBuilderException.cs
@@ -0,0 +1,7 @@
+namespace Bahla.Domain.Exceptions
+{
+ public sealed class EntityBuilderException : DomainException
+ {
+ internal EntityBuilderException(string message) : base(message) { }
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/AndNotSpecification.cs b/src/Bahla.Domain/Specifications/AndNotSpecification.cs
new file mode 100644
index 0000000..259a826
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/AndNotSpecification.cs
@@ -0,0 +1,14 @@
+using Bahla.Domain.Entities.Base;
+using Bahla.Domain.Specifications.Base;
+
+namespace Bahla.Domain.Specifications
+{
+ public sealed class AndNotSpecification(ISpecification left, ISpecification right) : CompositeSpecification where T : class
+ {
+ private readonly ISpecification _left = left;
+ private readonly ISpecification _right = right;
+
+ public override bool IsSatisfiedBy(T entity)
+ => _left.IsSatisfiedBy(entity) && !_right.IsSatisfiedBy(entity);
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/AndSpecification.cs b/src/Bahla.Domain/Specifications/AndSpecification.cs
new file mode 100644
index 0000000..1840fb0
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/AndSpecification.cs
@@ -0,0 +1,12 @@
+using Bahla.Domain.Specifications.Base;
+
+namespace Bahla.Domain.Specifications
+{
+ public sealed class AndSpecification(ISpecification left, ISpecification right) : CompositeSpecification where T : class
+ {
+ private readonly ISpecification _left = left;
+ private readonly ISpecification _right = right;
+ public override bool IsSatisfiedBy(T entity)
+ => _left.IsSatisfiedBy(entity) && _right.IsSatisfiedBy(entity);
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/Base/CompositeSpecification.cs b/src/Bahla.Domain/Specifications/Base/CompositeSpecification.cs
new file mode 100644
index 0000000..e229eda
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/Base/CompositeSpecification.cs
@@ -0,0 +1,17 @@
+namespace Bahla.Domain.Specifications.Base
+{
+ public abstract class CompositeSpecification : ISpecification where T : class
+ {
+ public abstract bool IsSatisfiedBy(T entity);
+ public ISpecification And(ISpecification other)
+ => new AndSpecification(this, other);
+ public ISpecification AndNot(ISpecification other)
+ => new AndNotSpecification(this, other);
+ public ISpecification Not()
+ => new NotSpecification(this);
+ public ISpecification Or(ISpecification other)
+ => new OrSpecification(this, other);
+ public ISpecification OrNot(ISpecification other)
+ => new OrNotSpecification(this, other);
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/Base/ISpecification.cs b/src/Bahla.Domain/Specifications/Base/ISpecification.cs
new file mode 100644
index 0000000..bbd42b6
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/Base/ISpecification.cs
@@ -0,0 +1,13 @@
+namespace Bahla.Domain.Specifications.Base
+{
+ public interface ISpecification where T : class
+ {
+ bool IsSatisfiedBy(T entity);
+ ISpecification And(ISpecification other);
+ ISpecification AndNot(ISpecification other);
+ ISpecification Or(ISpecification other);
+ ISpecification OrNot(ISpecification other);
+ ISpecification Not();
+
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/NotSpecification.cs b/src/Bahla.Domain/Specifications/NotSpecification.cs
new file mode 100644
index 0000000..3324370
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/NotSpecification.cs
@@ -0,0 +1,12 @@
+using Bahla.Domain.Entities.Base;
+using Bahla.Domain.Specifications.Base;
+
+namespace Bahla.Domain.Specifications
+{
+ public sealed class NotSpecification(ISpecification other) : CompositeSpecification where T : class
+ {
+ private readonly ISpecification _other = other;
+ public override bool IsSatisfiedBy(T entity)
+ => !_other.IsSatisfiedBy(entity);
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/OrNotSpecification.cs b/src/Bahla.Domain/Specifications/OrNotSpecification.cs
new file mode 100644
index 0000000..23d7a6a
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/OrNotSpecification.cs
@@ -0,0 +1,14 @@
+using Bahla.Domain.Entities.Base;
+using Bahla.Domain.Specifications.Base;
+
+namespace Bahla.Domain.Specifications
+{
+ public sealed class OrNotSpecification(ISpecification left, ISpecification right) : CompositeSpecification where T : class
+ {
+ private readonly ISpecification _left = left;
+ private readonly ISpecification _right = right;
+
+ public override bool IsSatisfiedBy(T entity)
+ => _left.IsSatisfiedBy(entity) || !_right.IsSatisfiedBy(entity);
+ }
+}
diff --git a/src/Bahla.Domain/Specifications/OrSpecification.cs b/src/Bahla.Domain/Specifications/OrSpecification.cs
new file mode 100644
index 0000000..3de8938
--- /dev/null
+++ b/src/Bahla.Domain/Specifications/OrSpecification.cs
@@ -0,0 +1,13 @@
+using Bahla.Domain.Specifications.Base;
+
+namespace Bahla.Domain.Specifications
+{
+ public sealed class OrSpecification(ISpecification left, ISpecification right) : CompositeSpecification where T : class
+ {
+ private readonly ISpecification _left = left;
+ private readonly ISpecification _right = right;
+
+ public override bool IsSatisfiedBy(T entity)
+ => _left.IsSatisfiedBy(entity) || _right.IsSatisfiedBy(entity);
+ }
+}
diff --git a/src/Bahla.Domain/Types/Identifier.cs b/src/Bahla.Domain/Types/Identifier.cs
new file mode 100644
index 0000000..fc3abe4
--- /dev/null
+++ b/src/Bahla.Domain/Types/Identifier.cs
@@ -0,0 +1,12 @@
+namespace Bahla.Domain.Types
+{
+ public readonly record struct Identifier(Guid UUID) : IComparable
+ {
+ public static implicit operator Identifier(Guid uuid) => new(uuid);
+ public static implicit operator Identifier(string uuid) => new(Guid.Parse(uuid));
+ public static Identifier Generate => Guid.NewGuid();
+ public override string ToString() => UUID.ToString();
+ public int CompareTo(Identifier other)
+ => UUID.CompareTo(other.UUID);
+ }
+}
diff --git a/src/Bahla.Persistence/Bahla.Persistence.csproj b/src/Bahla.Persistence/Bahla.Persistence.csproj
new file mode 100644
index 0000000..675a71d
--- /dev/null
+++ b/src/Bahla.Persistence/Bahla.Persistence.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Bahla.Persistence/Class1.cs b/src/Bahla.Persistence/Class1.cs
new file mode 100644
index 0000000..5728cf0
--- /dev/null
+++ b/src/Bahla.Persistence/Class1.cs
@@ -0,0 +1,7 @@
+namespace Bahla.Persistence
+{
+ public class Class1
+ {
+
+ }
+}
diff --git a/src/Bahla.Tests/Bahla.Tests.csproj b/src/Bahla.Tests/Bahla.Tests.csproj
index 8b5797e..d190730 100644
--- a/src/Bahla.Tests/Bahla.Tests.csproj
+++ b/src/Bahla.Tests/Bahla.Tests.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -17,8 +17,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Bahla.Tests/Builders/BuilderTests.cs b/src/Bahla.Tests/Builders/BuilderTests.cs
new file mode 100644
index 0000000..225ce22
--- /dev/null
+++ b/src/Bahla.Tests/Builders/BuilderTests.cs
@@ -0,0 +1,44 @@
+using Bahla.Domain.Entities;
+using Bahla.Domain.Exceptions;
+
+namespace Bahla.Tests.Builders
+{
+ internal class BuilderTests
+ {
+
+ [Test, Description("Test to create a role through builder.")]
+ public void TestRoleBuilder()
+ {
+ var role = Role.Builder
+ .WithName("Administrator")
+ .WithValueMask(1 << 0)
+ .Build();
+
+ var clonedRole = Role.Builder
+ .Clone(role)
+ .Build();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(role.RoleName, Is.EqualTo(clonedRole.RoleName));
+ Assert.That(role.ValueMask, Is.EqualTo(clonedRole.ValueMask));
+ Assert.That(role.UUID, Is.EqualTo(clonedRole.UUID));
+
+ Assert.That(clonedRole, Is.Not.SameAs(role));
+ });
+ }
+
+ [Test, Description("Test to create a role with a empty or null name.")]
+ public void TestRoleBuilderBadName()
+ {
+ Assert.Throws(() =>
+ {
+ Role.Builder
+ .WithName("")
+ .WithValueMask(1 << 0)
+ .Build();
+
+ });
+ }
+ }
+}
diff --git a/src/Bahla.Tests/Types/TypeIdentifierTests.cs b/src/Bahla.Tests/Types/TypeIdentifierTests.cs
new file mode 100644
index 0000000..2099bf1
--- /dev/null
+++ b/src/Bahla.Tests/Types/TypeIdentifierTests.cs
@@ -0,0 +1,39 @@
+using Bahla.Domain.Types;
+
+namespace Bahla.Tests.Entities
+{
+ internal class TypeIdentifierTests
+ {
+ const string StringIdentifier = "316f94a7-fb3b-44f0-abe1-88c2a3c99109";
+ const string StringIdentifier2 = "2c304fa1-19a4-432d-a729-73bdd45586ed";
+
+ [Test]
+ public void TestAssignmentGuid()
+ {
+ Identifier firstIdentifier = Guid.Parse(StringIdentifier);
+ Identifier secondIdentifier = Guid.Parse(StringIdentifier2);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(firstIdentifier, Is.Not.EqualTo(secondIdentifier));
+ Assert.That(firstIdentifier.ToString(), Is.EqualTo(StringIdentifier));
+ Assert.That(secondIdentifier.ToString(), Is.EqualTo(StringIdentifier2));
+ });
+ }
+
+ [Test]
+ public void TestAssignmentString()
+ {
+ Identifier firstIdentifier = StringIdentifier;
+ Identifier secondIdentifier = StringIdentifier2;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(firstIdentifier, Is.Not.EqualTo(secondIdentifier));
+ Assert.That(firstIdentifier.ToString(), Is.EqualTo(StringIdentifier));
+ Assert.That(secondIdentifier.ToString(), Is.EqualTo(StringIdentifier2));
+ });
+ }
+
+ }
+}
diff --git a/src/Bahla.Tests/UnitTest1.cs b/src/Bahla.Tests/UnitTest1.cs
deleted file mode 100644
index 9d13716..0000000
--- a/src/Bahla.Tests/UnitTest1.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Bahla.Tests
-{
- public class Tests
- {
- [SetUp]
- public void Setup()
- {
- }
-
- [Test]
- public void Test1()
- {
- Assert.Pass();
- }
- }
-}
\ No newline at end of file