docs: update application architecture and database patterns for request-level caching best practices

This commit is contained in:
Andras Bacsai 2025-12-08 13:46:31 +01:00
parent 5e8d11f732
commit a661ad796c
3 changed files with 68 additions and 5 deletions

View file

@ -283,14 +283,22 @@ ### **Polymorphic Relationships**
### **Team-Based Soft Scoping**
All major resources include team-based query scoping:
All major resources include team-based query scoping with request-level caching:
```php
// Automatic team filtering
$applications = Application::ownedByCurrentTeam()->get();
$servers = Server::ownedByCurrentTeam()->get();
// ✅ CORRECT - Use cached methods (request-level cache via once())
$applications = Application::ownedByCurrentTeamCached();
$servers = Server::ownedByCurrentTeamCached();
// ✅ CORRECT - Filter cached collection in memory
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
// Only use query builder when you need eager loading or fresh data
$projects = Project::ownedByCurrentTeam()->with('environments')->get();
```
See [Database Patterns](.ai/patterns/database-patterns.md#request-level-caching-with-ownedbycurrentteamcached) for full documentation.
### **Configuration Inheritance**
Environment variables cascade from:

View file

@ -243,6 +243,59 @@ ### Database Indexes
- **Composite indexes** for common queries
- **Unique constraints** for business rules
### Request-Level Caching with ownedByCurrentTeamCached()
Many models have both `ownedByCurrentTeam()` (returns query builder) and `ownedByCurrentTeamCached()` (returns cached collection). **Always prefer the cached version** to avoid duplicate database queries within the same request.
**Models with cached methods available:**
- `Server`, `PrivateKey`, `Project`
- `Application`
- `StandalonePostgresql`, `StandaloneMysql`, `StandaloneRedis`, `StandaloneMariadb`, `StandaloneMongodb`, `StandaloneKeydb`, `StandaloneDragonfly`, `StandaloneClickhouse`
- `Service`, `ServiceApplication`, `ServiceDatabase`
**Usage patterns:**
```php
// ✅ CORRECT - Uses request-level cache (via Laravel's once() helper)
$servers = Server::ownedByCurrentTeamCached();
// ❌ AVOID - Makes a new database query each time
$servers = Server::ownedByCurrentTeam()->get();
// ✅ CORRECT - Filter cached collection in memory
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
$server = Server::ownedByCurrentTeamCached()->firstWhere('id', $serverId);
$serverIds = Server::ownedByCurrentTeamCached()->pluck('id');
// ❌ AVOID - Making filtered database queries when data is already cached
$activeServers = Server::ownedByCurrentTeam()->where('is_active', true)->get();
```
**When to use which:**
- `ownedByCurrentTeamCached()` - **Default choice** for reading team data
- `ownedByCurrentTeam()` - Only when you need to chain query builder methods that can't be done on collections (like `with()` for eager loading), or when you explicitly need a fresh database query
**Implementation pattern for new models:**
```php
/**
* Get query builder for resources owned by current team.
* If you need all resources without further query chaining, use ownedByCurrentTeamCached() instead.
*/
public static function ownedByCurrentTeam()
{
return self::whereTeamId(currentTeam()->id);
}
/**
* Get all resources owned by current team (cached for request duration).
*/
public static function ownedByCurrentTeamCached()
{
return once(function () {
return self::ownedByCurrentTeam()->get();
});
}
```
## Data Consistency Patterns
### Database Transactions

View file

@ -222,6 +222,7 @@ ### Performance Considerations
- Queue heavy operations
- Optimize database queries with proper indexes
- Use chunking for large data operations
- **CRITICAL**: Use `ownedByCurrentTeamCached()` instead of `ownedByCurrentTeam()->get()`
### Code Style
- Follow PSR-12 coding standards
@ -318,3 +319,4 @@ ### Livewire & Frontend
Random other things you should remember:
- App\Models\Application::team must return a relationship instance., always use team()
- Always use `Model::ownedByCurrentTeamCached()` instead of `Model::ownedByCurrentTeam()->get()` for team-scoped queries to avoid duplicate database queries