bublliodocs
API

Μοτίβα REST API

Πώς δομούμε τα API endpoints, τους serializers, τα views και τα URLs.

Ροή αίτησης

Ένα authenticated request προς το API ακολουθεί αυτή τη ροή:

URL
  -> Authentication
  -> Organization membership and capability
  -> View
  -> Serializer
  -> Model
  -> Database

Και επιστρέφοντας:

Database
  -> Model
  -> Serializer
  -> JSON Response

Serializers

Οι serializers μετατρέπουν Django models σε JSON και επικυρώνουν τα εισερχόμενα δεδομένα.

Παράδειγμα αρχείου:

src/organizations/serializers.py

Παράδειγμα:

from rest_framework import serializers

from .models import Organization


class OrganizationSerializer(serializers.ModelSerializer):
    current_user_role = serializers.SerializerMethodField()

    class Meta:
        model = Organization
        fields = [
            "id",
            "name",
            "slug",
            "current_user_role",
            "created_at",
            "updated_at",
        ]

Ρόλος των βασικών πεδίων:

  • Το ModelSerializer παίρνει τους ορισμούς των fields από το model.
  • Το fields καθορίζει ποια δεδομένα εκθέτει το API.
  • Ο serializer επικυρώνει τα δεδομένα στα POST, PATCH και άλλα requests.

Views

Τα views ορίζουν τη συμπεριφορά των API endpoints.

Παράδειγμα αρχείου:

src/organizations/api/workspaces.py

Τα collection views για resources ενός Organization εντοπίζουν πρώτα το Organization μέσω του membership του authenticated χρήστη. Ακολουθεί απλοποιημένο παράδειγμα:

class CompanyListCreateAPIView(APIView):
    def get(self, request, organization_id):
        organization = get_organization_for_user(
            user=request.user,
            organization_id=organization_id,
            capability=Capability.VIEW_CRM,
        )
        companies = Company.objects.filter(organization=organization)
        serializer = CompanySerializer(companies, many=True)
        return Response(serializer.data)

    def post(self, request, organization_id):
        organization = get_organization_for_user(
            user=request.user,
            organization_id=organization_id,
            capability=Capability.MANAGE_CRM,
        )
        serializer = CompanySerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(organization=organization)
        return Response(serializer.data, status=201)

Σημεία που αξίζει να προσέξετε:

  • Το URL περιέχει το tenant ID.
  • Ο έλεγχος membership εξουσιοδοτεί τον χρήστη και εντοπίζει το Organization.
  • Το queryset φιλτράρεται με βάση το Organization που εντοπίστηκε.
  • Ο server ορίζει το organization κατά την αποθήκευση. Ο client δεν μπορεί να επιλέξει άλλο tenant μέσα στο JSON.
  • many=True σημαίνει ότι ο serializer λαμβάνει πολλά objects.
  • Το serializer.is_valid() επικυρώνει τα δεδομένα του request.
  • Το serializer.save() δημιουργεί database row.
  • Το Response(...) επιστρέφει JSON.

App URLs

Τα App URLs συνδέουν τις routes της εφαρμογής με τα αντίστοιχα views.

Παράδειγμα αρχείου:

src/organizations/urls.py

Παράδειγμα:

urlpatterns = [
    path("", OrganizationListCreateAPIView.as_view(), name="organization-list"),
    path("<uuid:organization_id>/", OrganizationDetailAPIView.as_view(), name="organization-detail"),
]

Σημασία των routes:

  • "" σημαίνει το root της route της εφαρμογής.
  • Το project URL configuration ορίζει το prefix.
  • Το .as_view() μετατρέπει την κλάση σε Django view.
  • Το <uuid:organization_id>/ διαβάζει UUID από το URL και το δίνει στο view ως organization_id.

Project URLs

Τα Project URLs συνδέουν τα global API prefixes με τα App URLs.

Παράδειγμα αρχείου:

src/bubllio_crm/urls.py

Παράδειγμα:

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api-auth/", include("rest_framework.urls")),
    path("api/v1/organizations/", include("organizations.urls")),
]

Αυτό δημιουργεί:

/api/v1/organizations/
/api/v1/organizations/<organization_id>/companies/
/api/v1/organizations/<organization_id>/contacts/
/api/v1/organizations/<organization_id>/automations/

Διαχωρισμός view classes

Χωρίστε τα view classes με βάση τον resource και την ευθύνη τους.

Collection endpoint:

GET  /api/v1/organizations/<organization_id>/companies/
POST /api/v1/organizations/<organization_id>/companies/

Χρησιμοποιήστε ένα class:

class CompanyListCreateAPIView(APIView):
    ...

Detail endpoint:

GET    /api/v1/organizations/<organization_id>/
DELETE /api/v1/organizations/<organization_id>/

Χρησιμοποιήστε ξεχωριστό class:

class OrganizationDetailAPIView(APIView):
    ...

Custom action endpoint:

POST /api/v1/organizations/<organization_id>/automations/<id>/test/

Χρησιμοποιήστε ξεχωριστό class:

class AutomationTestAPIView(APIView):
    ...

Γενικός κανόνας:

same path + same resource level -> same class, different methods
different path or custom action -> different class

Συμπεριφορά των automation responses

Η δημιουργία Automation αποθηκεύει ένα rule· δεν το εκτελεί. Το manual test endpoint εκτελεί την ενέργεια και επιστρέφει HTTP 201 μαζί με ένα run, ακόμη κι αν το run έχει status: failed. Ο client πρέπει να ελέγχει το status και το run.error_message στο response body. Το test δεν είναι dry run και παρακάμπτει το φίλτρο active rule που χρησιμοποιεί η κανονική αποστολή events.

Δεν υπάρχουν ακόμη PATCH/DELETE endpoints για Automations ούτε endpoint ορισμού multi-step workflow. Δείτε την τρέχουσα συμπεριφορά των Automations και το πρακτικό walkthrough. Οι μελλοντικοί workflow definitions στο Automation Roadmap δεν αποτελούν σημερινό API contract.

Αναζήτηση σε collection

Για απλή αναζήτηση, φιλτράρετε το queryset πριν περάσετε τα αποτελέσματα στον serializer.

Παράδειγμα:

from django.db.models import Q


def get(self, request, organization_id):
    organization = get_organization_for_user(
        user=request.user,
        organization_id=organization_id,
        capability=Capability.VIEW_CRM,
    )
    companies = Company.objects.filter(organization=organization)
    search = request.query_params.get("search")

    if search:
        companies = companies.filter(
            Q(name__icontains=search)
            | Q(email__icontains=search)
            | Q(phone_number__icontains=search)
            | Q(website__icontains=search)
        )

    serializer = CompanySerializer(companies, many=True)
    return Response(serializer.data)

Σημεία που αξίζει να προσέξετε:

  • Φιλτράρετε πριν από το serialization.
  • Χρησιμοποιήστε Q(...) | Q(...) για συνθήκη OR.
  • Πολλά keyword arguments στο .filter(...) συνδυάζονται με AND.

Naming convention

Προτεινόμενα ονόματα API classes:

OrganizationListCreateAPIView
OrganizationDetailAPIView
CompanyListCreateAPIView
CompanyDetailAPIView
ContactListCreateAPIView
ContactDetailAPIView
AutomationListCreateAPIView
AutomationRunListAPIView
AutomationTestAPIView

Το Detail είναι στον ενικό, επειδή το endpoint αφορά ένα resource.